/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// API class
package net.sourceforge.htmlunit.corejs.javascript;
import java.beans.
PropertyChangeEvent;
import java.beans.
PropertyChangeListener;
import java.io.
IOException;
import java.io.
InputStream;
import java.io.
PrintWriter;
import java.io.
Reader;
import java.io.
StringWriter;
import java.io.
Writer;
import java.lang.reflect.
InvocationTargetException;
import java.lang.reflect.
Method;
import java.net.
URL;
import java.util.
Enumeration;
import java.util.
HashMap;
import java.util.
HashSet;
import java.util.
Locale;
import java.util.
Map;
import java.util.
Set;
import java.util.jar.
Attributes;
import java.util.jar.
Manifest;
import net.sourceforge.htmlunit.corejs.javascript.ast.
AstRoot;
import net.sourceforge.htmlunit.corejs.javascript.ast.
ScriptNode;
import net.sourceforge.htmlunit.corejs.javascript.debug.
DebuggableScript;
import net.sourceforge.htmlunit.corejs.javascript.debug.
Debugger;
import net.sourceforge.htmlunit.corejs.javascript.xml.
XMLLib;
/**
* This class represents the runtime context of an executing script.
*
* Before executing a script, an instance of Context must be created
* and associated with the thread that will be executing the script.
* The Context will be used to store information about the executing
* of the script such as the call stack. Contexts are associated with
* the current thread using the {@link #call(ContextAction)}
* or {@link #enter()} methods.<p>
*
* Different forms of script execution are supported. Scripts may be
* evaluated from the source directly, or first compiled and then later
* executed. Interactive execution is also supported.<p>
*
* Some aspects of script execution, such as type conversions and
* object creation, may be accessed directly through methods of
* Context.
*
* @see Scriptable
* @author Norris Boyd
* @author Brendan Eich
*/
public class
Context
{
/**
* Language versions.
*
* All integral values are reserved for future version numbers.
*/
/**
* The unknown version.
* <p>Be aware, this version will not support many of the newer
* language features and will not change in the future.</p>
* <p>Please use one of the other constants like VERSION_ES6 to
* get support for recent language features.</p>
*/
public static final int
VERSION_UNKNOWN = -1;
/**
* The default version.
*/
public static final int
VERSION_DEFAULT = 0;
/**
* JavaScript 1.0
*/
public static final int
VERSION_1_0 = 100;
/**
* JavaScript 1.1
*/
public static final int
VERSION_1_1 = 110;
/**
* JavaScript 1.2
*/
public static final int
VERSION_1_2 = 120;
/**
* JavaScript 1.3
*/
public static final int
VERSION_1_3 = 130;
/**
* JavaScript 1.4
*/
public static final int
VERSION_1_4 = 140;
/**
* JavaScript 1.5
*/
public static final int
VERSION_1_5 = 150;
/**
* JavaScript 1.6
*/
public static final int
VERSION_1_6 = 160;
/**
* JavaScript 1.7
*/
public static final int
VERSION_1_7 = 170;
/**
* JavaScript 1.8
*/
public static final int
VERSION_1_8 = 180;
/**
* ECMAScript 6.
*/
public static final int
VERSION_ES6 = 200;
/**
* Controls behaviour of <tt>Date.prototype.getYear()</tt>.
* If <tt>hasFeature(FEATURE_NON_ECMA_GET_YEAR)</tt> returns true,
* Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000.
* The default behavior of {@link #hasFeature(int)} is always to subtruct
* 1900 as rquired by ECMAScript B.2.4.
*/
public static final int
FEATURE_NON_ECMA_GET_YEAR = 1;
/**
* Control if member expression as function name extension is available.
* If <tt>hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)</tt> returns
* true, allow <tt>function memberExpression(args) { body }</tt> to be
* syntax sugar for <tt>memberExpression = function(args) { body }</tt>,
* when memberExpression is not a simple identifier.
* See ECMAScript-262, section 11.2 for definition of memberExpression.
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2;
/**
* Control if reserved keywords are treated as identifiers.
* If <tt>hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER)</tt> returns true,
* treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary
* identifiers but warn about this usage.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3;
/**
* Control if <tt>toString()</tt> should returns the same result
* as <tt>toSource()</tt> when applied to objects and arrays.
* If <tt>hasFeature(FEATURE_TO_STRING_AS_SOURCE)</tt> returns true,
* calling <tt>toString()</tt> on JS objects gives the same result as
* calling <tt>toSource()</tt>. That is it returns JS source with code
* to create an object with all enumeratable fields of the original object
* instead of printing <tt>[object <i>result of
* {@link Scriptable#getClassName()}</i>]</tt>.
* <p>
* By default {@link #hasFeature(int)} returns true only if
* the current JS version is set to {@link #VERSION_1_2}.
*/
public static final int
FEATURE_TO_STRING_AS_SOURCE = 4;
/**
* Control if properties <tt>__proto__</tt> and <tt>__parent__</tt>
* are treated specially.
* If <tt>hasFeature(FEATURE_PARENT_PROTO_PROPERTIES)</tt> returns true,
* treat <tt>__parent__</tt> and <tt>__proto__</tt> as special properties.
* <p>
* The properties allow to query and set scope and prototype chains for the
* objects. The special meaning of the properties is available
* only when they are used as the right hand side of the dot operator.
* For example, while <tt>x.__proto__ = y</tt> changes the prototype
* chain of the object <tt>x</tt> to point to <tt>y</tt>,
* <tt>x["__proto__"] = y</tt> simply assigns a new value to the property
* <tt>__proto__</tt> in <tt>x</tt> even when the feature is on.
*
* By default {@link #hasFeature(int)} returns true.
*/
public static final int
FEATURE_PARENT_PROTO_PROPERTIES = 5;
/**
* @deprecated In previous releases, this name was given to
* FEATURE_PARENT_PROTO_PROPERTIES.
*/
@
Deprecated
public static final int
FEATURE_PARENT_PROTO_PROPRTIES = 5;
/**
* Control if support for E4X(ECMAScript for XML) extension is available.
* If hasFeature(FEATURE_E4X) returns true, the XML syntax is available.
* <p>
* By default {@link #hasFeature(int)} returns true if
* the current JS version is set to {@link #VERSION_DEFAULT}
* or is at least {@link #VERSION_1_6}.
* @since 1.6 Release 1
*/
public static final int
FEATURE_E4X = 6;
/**
* Control if dynamic scope should be used for name access.
* If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup
* during name resolution will use the top scope of the script or function
* which is at the top of JS execution stack instead of the top scope of the
* script or function from the current stack frame if the top scope of
* the top stack frame contains the top scope of the current stack frame
* on its prototype chain.
* <p>
* This is useful to define shared scope containing functions that can
* be called from scripts and functions using private scopes.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int
FEATURE_DYNAMIC_SCOPE = 7;
/**
* Control if strict variable mode is enabled.
* When the feature is on Rhino reports runtime errors if assignment
* to a global variable that does not exist is executed. When the feature
* is off such assignments create a new variable in the global scope as
* required by ECMA 262.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int
FEATURE_STRICT_VARS = 8;
/**
* Control if strict eval mode is enabled.
* When the feature is on Rhino reports runtime errors if non-string
* argument is passed to the eval function. When the feature is off
* eval simply return non-string argument as is without performing any
* evaluation as required by ECMA 262.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 1
*/
public static final int
FEATURE_STRICT_EVAL = 9;
/**
* When the feature is on Rhino will add a "fileName" and "lineNumber"
* properties to Error objects automatically. When the feature is off, you
* have to explicitly pass them as the second and third argument to the
* Error constructor. Note that neither behavior is fully ECMA 262
* compliant (as 262 doesn't specify a three-arg constructor), but keeping
* the feature off results in Error objects that don't have
* additional non-ECMA properties when constructed using the ECMA-defined
* single-arg constructor and is thus desirable if a stricter ECMA
* compliance is desired, specifically adherence to the point 15.11.5. of
* the standard.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 6
*/
public static final int
FEATURE_LOCATION_INFORMATION_IN_ERROR = 10;
/**
* Controls whether JS 1.5 'strict mode' is enabled.
* When the feature is on, Rhino reports more than a dozen different
* warnings. When the feature is off, these warnings are not generated.
* FEATURE_STRICT_MODE implies FEATURE_STRICT_VARS and FEATURE_STRICT_EVAL.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.6 Release 6
*/
public static final int
FEATURE_STRICT_MODE = 11;
/**
* Controls whether a warning should be treated as an error.
* @since 1.6 Release 6
*/
public static final int
FEATURE_WARNING_AS_ERROR = 12;
/**
* Enables enhanced access to Java.
* Specifically, controls whether private and protected members can be
* accessed, and whether scripts can catch all Java exceptions.
* <p>
* Note that this feature should only be enabled for trusted scripts.
* <p>
* By default {@link #hasFeature(int)} returns false.
* @since 1.7 Release 1
*/
public static final int
FEATURE_ENHANCED_JAVA_ACCESS = 13;
/**
* Enables access to JavaScript features from ECMAscript 6 that are present in
* JavaScript engines that do not yet support version 6, such as V8.
* This includes support for typed arrays. Default is true.
* @since 1.7 Release 3
*/
public static final int
FEATURE_V8_EXTENSIONS = 14;
/**
* Defines how an undefined "this" parameter is handled in certain calls. Previously Rhino
* would convert an undefined "this" to null, whereas recent specs call for it to be treated
* differently. Default is to be set if language version <= 1.7.
* @since 1.7.7
*/
public static final int
FEATURE_OLD_UNDEF_NULL_THIS = 15;
/**
* If set, then the order of property key enumeration will be first numeric keys in numeric order,
* followed by string keys in order of creation, and finally Symbol keys, as specified in ES6.
* Default is true for language version >= "ES6" and false otherwise.
* @since 1.7.7.1
*/
public static final int
FEATURE_ENUMERATE_IDS_FIRST = 16;
/**
* If set, then all objects will have a thread-safe property map. (Note that this doesn't make
* everything else that they do thread-safe -- that depends on the specific implementation.
* If not set, users should not share Rhino objects between threads, unless the "sync"
* function is used to wrap them with an explicit synchronizer. The default
* is false, which means that by default, individual objects are not thread-safe.
* @since 1.7.8
*/
public static final int
FEATURE_THREAD_SAFE_OBJECTS = 17;
/**
* If set, then all integer numbers will be returned without decimal place. For instance
* assume there is a function like this:
* <code>function foo() {return 5;}</code>
* 5 will be returned if feature is set, 5.0 otherwise.
*/
public static final int
FEATURE_INTEGER_WITHOUT_DECIMAL_PLACE = 18;
/**
* TypedArray buffer uses little/big endian depending on the platform.
* The default is big endian for Rhino.
* @since 1.7 Release 11
*/
public static final int
FEATURE_LITTLE_ENDIAN = 19;
/**
* Special to HtmlUnit's Rhino fork.
*
* Whether the "someFunc.arguments" is a read-only view of the function argument
* or the real arguments.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_HTMLUNIT_FN_ARGUMENTS_IS_RO_VIEW = 101;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that 'exception' (technically NativeError) always exposes "stack" property.
*
* By default {@link #hasFeature(int)} returns true.
*/
public static final int
FEATURE_HTMLUNIT_ERROR_STACK = 102;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that function is defined even before its declaration, inside a block.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_HTMLUNIT_FUNCTION_DECLARED_FORWARD_IN_BLOCK = 103;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that for(x in []) should enumerate the numbers first.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_HTMLUNIT_ENUM_NUMBERS_FIRST = 104;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that {@code MemberBox.toString()} contains property name.
*
* By default {@link #hasFeature(int)} returns true.
*/
public static final int
FEATURE_HTMLUNIT_MEMBERBOX_NAME = 105;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that {@code MemberBox.toString()} starts with new line.
*
* By default {@link #hasFeature(int)} returns true.
*/
public static final int
FEATURE_HTMLUNIT_MEMBERBOX_NEWLINE = 106;
/**
* Special to HtmlUnit's Rhino fork.
*
* Indicates that {@code Array} supports constructor properties.
*
* By default {@link #hasFeature(int)} returns false.
*/
public static final int
FEATURE_HTMLUNIT_ARRAY_PROPERTIES = 107;
public static final
String languageVersionProperty = "language version";
public static final
String errorReporterProperty = "error reporter";
/**
* Convenient value to use as zero-length array of objects.
*/
public static final
Object[]
emptyArgs =
ScriptRuntime.
emptyArgs;
/**
* Creates a new Context. The context will be associated with the {@link
* ContextFactory#getGlobal() global context factory}.
*
* Note that the Context must be associated with a thread before
* it can be used to execute a script.
* @deprecated this constructor is deprecated because it creates a
* dependency on a static singleton context factory. Use
* {@link ContextFactory#enter()} or
* {@link ContextFactory#call(ContextAction)} instead. If you subclass
* this class, consider using {@link #Context(ContextFactory)} constructor
* instead in the subclasses' constructors.
*/
@
Deprecated
public
Context()
{
this(
ContextFactory.
getGlobal());
}
/**
* Creates a new context. Provided as a preferred super constructor for
* subclasses in place of the deprecated default public constructor.
* @param factory the context factory associated with this context (most
* likely, the one that created the context). Can not be null. The context
* features are inherited from the factory, and the context will also
* otherwise use its factory's services.
* @throws IllegalArgumentException if factory parameter is null.
*/
protected
Context(
ContextFactory factory)
{
if(
factory == null) {
throw new
IllegalArgumentException("factory == null");
}
this.
factory =
factory;
version =
VERSION_DEFAULT;
optimizationLevel =
codegenClass != null ? 0 : -1;
maximumInterpreterStackDepth =
Integer.
MAX_VALUE;
}
/**
* Get the current Context.
*
* The current Context is per-thread; this method looks up
* the Context associated with the current thread. <p>
*
* @return the Context associated with the current thread, or
* null if no context is associated with the current
* thread.
* @see ContextFactory#enterContext()
* @see ContextFactory#call(ContextAction)
*/
public static
Context getCurrentContext()
{
Object helper =
VMBridge.
instance.
getThreadContextHelper();
return
VMBridge.
instance.
getContext(
helper);
}
/**
* Same as calling {@link ContextFactory#enterContext()} on the global
* ContextFactory instance.
* @return a Context associated with the current thread
* @see #getCurrentContext()
* @see #exit()
* @see #call(ContextAction)
*/
public static
Context enter()
{
return
enter(null);
}
/**
* Get a Context associated with the current thread, using
* the given Context if need be.
* <p>
* The same as <code>enter()</code> except that <code>cx</code>
* is associated with the current thread and returned if
* the current thread has no associated context and <code>cx</code>
* is not associated with any other thread.
* @param cx a Context to associate with the thread if possible
* @return a Context associated with the current thread
* @deprecated use {@link ContextFactory#enterContext(Context)} instead as
* this method relies on usage of a static singleton "global" ContextFactory.
* @see ContextFactory#enterContext(Context)
* @see ContextFactory#call(ContextAction)
*/
@
Deprecated
public static
Context enter(
Context cx)
{
return
enter(
cx,
ContextFactory.
getGlobal());
}
static final
Context enter(
Context cx,
ContextFactory factory)
{
Object helper =
VMBridge.
instance.
getThreadContextHelper();
Context old =
VMBridge.
instance.
getContext(
helper);
if (
old != null) {
cx =
old;
} else {
if (
cx == null) {
cx =
factory.
makeContext();
if (
cx.
enterCount != 0) {
throw new
IllegalStateException("factory.makeContext() returned Context instance already associated with some thread");
}
factory.
onContextCreated(
cx);
if (
factory.
isSealed() && !
cx.
isSealed()) {
cx.
seal(null);
}
} else {
if (
cx.
enterCount != 0) {
throw new
IllegalStateException("can not use Context instance already associated with some thread");
}
}
VMBridge.
instance.
setContext(
helper,
cx);
}
++
cx.
enterCount;
return
cx;
}
/**
* Exit a block of code requiring a Context.
*
* Calling <code>exit()</code> will remove the association between
* the current thread and a Context if the prior call to
* {@link ContextFactory#enterContext()} on this thread newly associated a
* Context with this thread. Once the current thread no longer has an
* associated Context, it cannot be used to execute JavaScript until it is
* again associated with a Context.
* @see ContextFactory#enterContext()
*/
public static void
exit()
{
Object helper =
VMBridge.
instance.
getThreadContextHelper();
Context cx =
VMBridge.
instance.
getContext(
helper);
if (
cx == null) {
throw new
IllegalStateException(
"Calling Context.exit without previous Context.enter");
}
if (
cx.
enterCount < 1)
Kit.
codeBug();
if (--
cx.
enterCount == 0) {
VMBridge.
instance.
setContext(
helper, null);
cx.
factory.
onContextReleased(
cx);
}
}
/**
* Call {@link ContextAction#run(Context cx)}
* using the Context instance associated with the current thread.
* If no Context is associated with the thread, then
* <tt>ContextFactory.getGlobal().makeContext()</tt> will be called to
* construct new Context instance. The instance will be temporary
* associated with the thread during call to
* {@link ContextAction#run(Context)}.
* @deprecated use {@link ContextFactory#call(ContextAction)} instead as
* this method relies on usage of a static singleton "global"
* ContextFactory.
* @return The result of {@link ContextAction#run(Context)}.
*/
@
Deprecated
public static <T> T
call(
ContextAction<T>
action)
{
return
call(
ContextFactory.
getGlobal(),
action);
}
/**
* Call {@link
* Callable#call(Context cx, Scriptable scope, Scriptable thisObj,
* Object[] args)}
* using the Context instance associated with the current thread.
* If no Context is associated with the thread, then
* {@link ContextFactory#makeContext()} will be called to construct
* new Context instance. The instance will be temporary associated
* with the thread during call to {@link ContextAction#run(Context)}.
* <p>
* It is allowed but not advisable to use null for <tt>factory</tt>
* argument in which case the global static singleton ContextFactory
* instance will be used to create new context instances.
* @see ContextFactory#call(ContextAction)
*/
public static
Object call(
ContextFactory factory, final
Callable callable,
final
Scriptable scope, final
Scriptable thisObj,
final
Object[]
args)
{
if(
factory == null) {
factory =
ContextFactory.
getGlobal();
}
return
call(
factory,
cx ->
callable.
call(
cx,
scope,
thisObj,
args));
}
/**
* The method implements {@link ContextFactory#call(ContextAction)} logic.
*/
static <T> T
call(
ContextFactory factory,
ContextAction<T>
action) {
Context cx =
enter(null,
factory);
try {
return
action.
run(
cx);
}
finally {
exit();
}
}
/**
* @deprecated
* @see ContextFactory#addListener(net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener)
* @see ContextFactory#getGlobal()
*/
@
Deprecated
public static void
addContextListener(
ContextListener listener)
{
// Special workaround for the debugger
String DBG = "net.sourceforge.htmlunit.corejs.javascript.tools.debugger.Main";
if (
DBG.
equals(
listener.
getClass().
getName())) {
Class<?>
cl =
listener.
getClass();
Class<?>
factoryClass =
Kit.
classOrNull(
"net.sourceforge.htmlunit.corejs.javascript.ContextFactory");
Class<?>[]
sig = {
factoryClass };
Object[]
args = {
ContextFactory.
getGlobal() };
try {
Method m =
cl.
getMethod("attachTo",
sig);
m.
invoke(
listener,
args);
} catch (
Exception ex) {
RuntimeException rex = new
RuntimeException();
Kit.
initCause(
rex,
ex);
throw
rex;
}
return;
}
ContextFactory.
getGlobal().
addListener(
listener);
}
/**
* @deprecated
* @see ContextFactory#removeListener(net.sourceforge.htmlunit.corejs.javascript.ContextFactory.Listener)
* @see ContextFactory#getGlobal()
*/
@
Deprecated
public static void
removeContextListener(
ContextListener listener)
{
ContextFactory.
getGlobal().
addListener(
listener);
}
/**
* Return {@link ContextFactory} instance used to create this Context.
*/
public final
ContextFactory getFactory()
{
return
factory;
}
/**
* Checks if this is a sealed Context. A sealed Context instance does not
* allow to modify any of its properties and will throw an exception
* on any such attempt.
* @see #seal(Object sealKey)
*/
public final boolean
isSealed()
{
return
sealed;
}
/**
* Seal this Context object so any attempt to modify any of its properties
* including calling {@link #enter()} and {@link #exit()} methods will
* throw an exception.
* <p>
* If <tt>sealKey</tt> is not null, calling
* {@link #unseal(Object sealKey)} with the same key unseals
* the object. If <tt>sealKey</tt> is null, unsealing is no longer possible.
*
* @see #isSealed()
* @see #unseal(Object)
*/
public final void
seal(
Object sealKey)
{
if (
sealed)
onSealedMutation();
sealed = true;
this.
sealKey =
sealKey;
}
/**
* Unseal previously sealed Context object.
* The <tt>sealKey</tt> argument should not be null and should match
* <tt>sealKey</tt> suplied with the last call to
* {@link #seal(Object)} or an exception will be thrown.
*
* @see #isSealed()
* @see #seal(Object sealKey)
*/
public final void
unseal(
Object sealKey)
{
if (
sealKey == null) throw new
IllegalArgumentException();
if (this.
sealKey !=
sealKey) throw new
IllegalArgumentException();
if (!
sealed) throw new
IllegalStateException();
sealed = false;
this.
sealKey = null;
}
static void
onSealedMutation()
{
throw new
IllegalStateException();
}
/**
* Get the current language version.
* <p>
* The language version number affects JavaScript semantics as detailed
* in the overview documentation.
*
* @return an integer that is one of VERSION_1_0, VERSION_1_1, etc.
*/
public final int
getLanguageVersion()
{
return
version;
}
/**
* Set the language version.
*
* <p>
* Setting the language version will affect functions and scripts compiled
* subsequently. See the overview documentation for version-specific
* behavior.
*
* @param version the version as specified by VERSION_1_0, VERSION_1_1, etc.
*/
public void
setLanguageVersion(int
version)
{
if (
sealed)
onSealedMutation();
checkLanguageVersion(
version);
Object listeners =
propertyListeners;
if (
listeners != null &&
version != this.
version) {
firePropertyChangeImpl(
listeners,
languageVersionProperty,
Integer.
valueOf(this.
version),
Integer.
valueOf(
version));
}
this.
version =
version;
}
public static boolean
isValidLanguageVersion(int
version)
{
switch (
version) {
case
VERSION_DEFAULT:
case
VERSION_1_0:
case
VERSION_1_1:
case
VERSION_1_2:
case
VERSION_1_3:
case
VERSION_1_4:
case
VERSION_1_5:
case
VERSION_1_6:
case
VERSION_1_7:
case
VERSION_1_8:
case
VERSION_ES6:
return true;
}
return false;
}
public static void
checkLanguageVersion(int
version)
{
if (
isValidLanguageVersion(
version)) {
return;
}
throw new
IllegalArgumentException("Bad language version: "+
version);
}
/**
* Get the implementation version.
*
* <p>
* The implementation version is of the form
* <pre>
* "<i>name langVer</i> <code>release</code> <i>relNum date</i>"
* </pre>
* where <i>name</i> is the name of the product, <i>langVer</i> is
* the language version, <i>relNum</i> is the release number, and
* <i>date</i> is the release date for that specific
* release in the form "yyyy mm dd".
*
* @return a string that encodes the product, language version, release
* number, and date.
*/
public final
String getImplementationVersion()
{
if (
implementationVersion == null) {
Enumeration<
URL>
urls;
try {
urls =
Context.class.
getClassLoader().
getResources("META-INF/MANIFEST.MF");
} catch (
IOException ioe) {
return null;
}
// There will be many manifests in the world -- enumerate all of them until we find the right one.
while (
urls.
hasMoreElements()) {
URL metaUrl =
urls.
nextElement();
InputStream is = null;
try {
is =
metaUrl.
openStream();
Manifest mf = new
Manifest(
is);
Attributes attrs =
mf.
getMainAttributes();
if ("Mozilla Rhino".
equals(
attrs.
getValue("Implementation-Title"))) {
implementationVersion =
"Rhino " +
attrs.
getValue("Implementation-Version") + " " +
attrs.
getValue("Built-Date").
replaceAll("-", " ");
return
implementationVersion;
}
} catch (
IOException e) {
// Ignore this unlikely event
} finally {
try {
if (
is != null)
is.
close();
} catch (
IOException e) {
// Ignore this even unlikelier event
}
}
}
}
return
implementationVersion;
}
/**
* Get the current error reporter.
*
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public final
ErrorReporter getErrorReporter()
{
if (
errorReporter == null) {
return
DefaultErrorReporter.
instance;
}
return
errorReporter;
}
/**
* Change the current error reporter.
*
* @return the previous error reporter
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public final
ErrorReporter setErrorReporter(
ErrorReporter reporter)
{
if (
sealed)
onSealedMutation();
if (
reporter == null) throw new
IllegalArgumentException();
ErrorReporter old =
getErrorReporter();
if (
reporter ==
old) {
return
old;
}
Object listeners =
propertyListeners;
if (
listeners != null) {
firePropertyChangeImpl(
listeners,
errorReporterProperty,
old,
reporter);
}
this.
errorReporter =
reporter;
return
old;
}
/**
* Get the current locale. Returns the default locale if none has
* been set.
*
* @see java.util.Locale
*/
public final
Locale getLocale()
{
if (
locale == null)
locale =
Locale.
getDefault();
return
locale;
}
/**
* Set the current locale.
*
* @see java.util.Locale
*/
public final
Locale setLocale(
Locale loc)
{
if (
sealed)
onSealedMutation();
Locale result =
locale;
locale =
loc;
return
result;
}
/**
* Register an object to receive notifications when a bound property
* has changed
* @see java.beans.PropertyChangeEvent
* @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
* @param l the listener
*/
public final void
addPropertyChangeListener(
PropertyChangeListener l)
{
if (
sealed)
onSealedMutation();
propertyListeners =
Kit.
addListener(
propertyListeners,
l);
}
/**
* Remove an object from the list of objects registered to receive
* notification of changes to a bounded property
* @see java.beans.PropertyChangeEvent
* @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
* @param l the listener
*/
public final void
removePropertyChangeListener(
PropertyChangeListener l)
{
if (
sealed)
onSealedMutation();
propertyListeners =
Kit.
removeListener(
propertyListeners,
l);
}
/**
* Notify any registered listeners that a bounded property has changed
* @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
* @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
* @see java.beans.PropertyChangeListener
* @see java.beans.PropertyChangeEvent
* @param property the bound property
* @param oldValue the old value
* @param newValue the new value
*/
final void
firePropertyChange(
String property,
Object oldValue,
Object newValue)
{
Object listeners =
propertyListeners;
if (
listeners != null) {
firePropertyChangeImpl(
listeners,
property,
oldValue,
newValue);
}
}
private void
firePropertyChangeImpl(
Object listeners,
String property,
Object oldValue,
Object newValue)
{
for (int
i = 0; ; ++
i) {
Object l =
Kit.
getListener(
listeners,
i);
if (
l == null)
break;
if (
l instanceof
PropertyChangeListener) {
PropertyChangeListener pcl = (
PropertyChangeListener)
l;
pcl.
propertyChange(new
PropertyChangeEvent(
this,
property,
oldValue,
newValue));
}
}
}
/**
* Report a warning using the error reporter for the current thread.
*
* @param message the warning message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static void
reportWarning(
String message,
String sourceName,
int
lineno,
String lineSource,
int
lineOffset)
{
Context cx =
Context.
getContext();
if (
cx.
hasFeature(
FEATURE_WARNING_AS_ERROR))
reportError(
message,
sourceName,
lineno,
lineSource,
lineOffset);
else
cx.
getErrorReporter().
warning(
message,
sourceName,
lineno,
lineSource,
lineOffset);
}
/**
* Report a warning using the error reporter for the current thread.
*
* @param message the warning message to report
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static void
reportWarning(
String message)
{
int[]
linep = { 0 };
String filename =
getSourcePositionFromStack(
linep);
Context.
reportWarning(
message,
filename,
linep[0], null, 0);
}
public static void
reportWarning(
String message,
Throwable t)
{
int[]
linep = { 0 };
String filename =
getSourcePositionFromStack(
linep);
Writer sw = new
StringWriter();
PrintWriter pw = new
PrintWriter(
sw);
pw.
println(
message);
t.
printStackTrace(
pw);
pw.
flush();
Context.
reportWarning(
sw.
toString(),
filename,
linep[0], null, 0);
}
/**
* Report an error using the error reporter for the current thread.
*
* @param message the error message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static void
reportError(
String message,
String sourceName,
int
lineno,
String lineSource,
int
lineOffset)
{
Context cx =
getCurrentContext();
if (
cx != null) {
cx.
getErrorReporter().
error(
message,
sourceName,
lineno,
lineSource,
lineOffset);
} else {
throw new
EvaluatorException(
message,
sourceName,
lineno,
lineSource,
lineOffset);
}
}
/**
* Report an error using the error reporter for the current thread.
*
* @param message the error message to report
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static void
reportError(
String message)
{
int[]
linep = { 0 };
String filename =
getSourcePositionFromStack(
linep);
Context.
reportError(
message,
filename,
linep[0], null, 0);
}
/**
* Report a runtime error using the error reporter for the current thread.
*
* @param message the error message to report
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param lineSource the text of the line (may be null)
* @param lineOffset the offset into lineSource where problem was detected
* @return a runtime exception that will be thrown to terminate the
* execution of the script
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static
EvaluatorException reportRuntimeError(
String message,
String sourceName,
int
lineno,
String lineSource,
int
lineOffset)
{
Context cx =
getCurrentContext();
if (
cx != null) {
return
cx.
getErrorReporter().
runtimeError(
message,
sourceName,
lineno,
lineSource,
lineOffset);
}
throw new
EvaluatorException(
message,
sourceName,
lineno,
lineSource,
lineOffset);
}
static
EvaluatorException reportRuntimeError0(
String messageId)
{
String msg =
ScriptRuntime.
getMessage0(
messageId);
return
reportRuntimeError(
msg);
}
static
EvaluatorException reportRuntimeError1(
String messageId,
Object arg1)
{
String msg =
ScriptRuntime.
getMessage1(
messageId,
arg1);
return
reportRuntimeError(
msg);
}
static
EvaluatorException reportRuntimeError2(
String messageId,
Object arg1,
Object arg2)
{
String msg =
ScriptRuntime.
getMessage2(
messageId,
arg1,
arg2);
return
reportRuntimeError(
msg);
}
static
EvaluatorException reportRuntimeError3(
String messageId,
Object arg1,
Object arg2,
Object arg3)
{
String msg =
ScriptRuntime.
getMessage3(
messageId,
arg1,
arg2,
arg3);
return
reportRuntimeError(
msg);
}
static
EvaluatorException reportRuntimeError4(
String messageId,
Object arg1,
Object arg2,
Object arg3,
Object arg4)
{
String msg
=
ScriptRuntime.
getMessage4(
messageId,
arg1,
arg2,
arg3,
arg4);
return
reportRuntimeError(
msg);
}
/**
* Report a runtime error using the error reporter for the current thread.
*
* @param message the error message to report
* @see net.sourceforge.htmlunit.corejs.javascript.ErrorReporter
*/
public static
EvaluatorException reportRuntimeError(
String message)
{
int[]
linep = { 0 };
String filename =
getSourcePositionFromStack(
linep);
return
Context.
reportRuntimeError(
message,
filename,
linep[0], null, 0);
}
/**
* Initialize the standard objects.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.
*
* @return the initialized scope
*/
public final
ScriptableObject initStandardObjects()
{
return
initStandardObjects(null, false);
}
/**
* Initialize the standard objects, leaving out those that offer access directly
* to Java classes. This sets up "scope" to have access to all the standard
* JavaScript classes, but does not create global objects for any top-level
* Java packages. In addition, the "Packages," "JavaAdapter," and
* "JavaImporter" classes, and the "getClass" function, are not
* initialized.
*
* The result of this function is a scope that may be safely used in a "sandbox"
* environment where it is not desirable to give access to Java code from JavaScript.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.
*
* @return the initialized scope
*/
public final
ScriptableObject initSafeStandardObjects()
{
return
initSafeStandardObjects(null, false);
}
/**
* Initialize the standard objects.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.
*
* @param scope the scope to initialize, or null, in which case a new
* object will be created to serve as the scope
* @return the initialized scope. The method returns the value of the scope
* argument if it is not null or newly allocated scope object which
* is an instance {@link ScriptableObject}.
*/
public final
Scriptable initStandardObjects(
ScriptableObject scope)
{
return
initStandardObjects(
scope, false);
}
/**
* Initialize the standard objects, leaving out those that offer access directly
* to Java classes. This sets up "scope" to have access to all the standard
* JavaScript classes, but does not create global objects for any top-level
* Java packages. In addition, the "Packages," "JavaAdapter," and
* "JavaImporter" classes, and the "getClass" function, are not
* initialized.
*
* The result of this function is a scope that may be safely used in a "sandbox"
* environment where it is not desirable to give access to Java code from JavaScript.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.
*
* @param scope the scope to initialize, or null, in which case a new
* object will be created to serve as the scope
* @return the initialized scope. The method returns the value of the scope
* argument if it is not null or newly allocated scope object which
* is an instance {@link ScriptableObject}.
*/
public final
Scriptable initSafeStandardObjects(
ScriptableObject scope)
{
return
initSafeStandardObjects(
scope, false);
}
/**
* Initialize the standard objects.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.<p>
*
* This form of the method also allows for creating "sealed" standard
* objects. An object that is sealed cannot have properties added, changed,
* or removed. This is useful to create a "superglobal" that can be shared
* among several top-level objects. Note that sealing is not allowed in
* the current ECMA/ISO language specification, but is likely for
* the next version.
*
* @param scope the scope to initialize, or null, in which case a new
* object will be created to serve as the scope
* @param sealed whether or not to create sealed standard objects that
* cannot be modified.
* @return the initialized scope. The method returns the value of the scope
* argument if it is not null or newly allocated scope object.
* @since 1.4R3
*/
public
ScriptableObject initStandardObjects(
ScriptableObject scope,
boolean
sealed)
{
return
ScriptRuntime.
initStandardObjects(this,
scope,
sealed);
}
/**
* Initialize the standard objects, leaving out those that offer access directly
* to Java classes. This sets up "scope" to have access to all the standard
* JavaScript classes, but does not create global objects for any top-level
* Java packages. In addition, the "Packages," "JavaAdapter," and
* "JavaImporter" classes, and the "getClass" function, are not
* initialized.
*
* The result of this function is a scope that may be safely used in a "sandbox"
* environment where it is not desirable to give access to Java code from JavaScript.
*
* Creates instances of the standard objects and their constructors
* (Object, String, Number, Date, etc.), setting up 'scope' to act
* as a global object as in ECMA 15.1.<p>
*
* This method must be called to initialize a scope before scripts
* can be evaluated in that scope.<p>
*
* This method does not affect the Context it is called upon.<p>
*
* This form of the method also allows for creating "sealed" standard
* objects. An object that is sealed cannot have properties added, changed,
* or removed. This is useful to create a "superglobal" that can be shared
* among several top-level objects. Note that sealing is not allowed in
* the current ECMA/ISO language specification, but is likely for
* the next version.
*
* @param scope the scope to initialize, or null, in which case a new
* object will be created to serve as the scope
* @param sealed whether or not to create sealed standard objects that
* cannot be modified.
* @return the initialized scope. The method returns the value of the scope
* argument if it is not null or newly allocated scope object.
* @since 1.7.6
*/
public
ScriptableObject initSafeStandardObjects(
ScriptableObject scope,
boolean
sealed)
{
return
ScriptRuntime.
initSafeStandardObjects(this,
scope,
sealed);
}
/**
* Get the singleton object that represents the JavaScript Undefined value.
*/
public static
Object getUndefinedValue()
{
return
Undefined.
instance;
}
/**
* Evaluate a JavaScript source string.
*
* The provided source name and line number are used for error messages
* and for producing debug information.
*
* @param scope the scope to execute in
* @param source the JavaScript source
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param securityDomain an arbitrary object that specifies security
* information about the origin or owner of the script. For
* implementations that don't care about security, this value
* may be null.
* @return the result of evaluating the string
* @see net.sourceforge.htmlunit.corejs.javascript.SecurityController
*/
public final
Object evaluateString(
Scriptable scope,
String source,
String sourceName, int
lineno,
Object securityDomain)
{
Script script =
compileString(
source,
sourceName,
lineno,
securityDomain);
if (
script != null) {
return
script.
exec(this,
scope);
}
return null;
}
/**
* Evaluate a reader as JavaScript source.
*
* All characters of the reader are consumed.
*
* @param scope the scope to execute in
* @param in the Reader to get JavaScript source from
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param securityDomain an arbitrary object that specifies security
* information about the origin or owner of the script. For
* implementations that don't care about security, this value
* may be null.
* @return the result of evaluating the source
*
* @exception IOException if an IOException was generated by the Reader
*/
public final
Object evaluateReader(
Scriptable scope,
Reader in,
String sourceName, int
lineno,
Object securityDomain)
throws
IOException
{
Script script =
compileReader(
scope,
in,
sourceName,
lineno,
securityDomain);
if (
script != null) {
return
script.
exec(this,
scope);
}
return null;
}
/**
* Execute script that may pause execution by capturing a continuation.
* Caller must be prepared to catch a ContinuationPending exception
* and resume execution by calling
* {@link #resumeContinuation(Object, Scriptable, Object)}.
* @param script The script to execute. Script must have been compiled
* with interpreted mode (optimization level -1)
* @param scope The scope to execute the script against
* @throws ContinuationPending if the script calls a function that results
* in a call to {@link #captureContinuation()}
* @since 1.7 Release 2
*/
public
Object executeScriptWithContinuations(
Script script,
Scriptable scope)
throws
ContinuationPending
{
if (!(
script instanceof
InterpretedFunction) ||
!((
InterpretedFunction)
script).
isScript())
{
// Can only be applied to scripts
throw new
IllegalArgumentException("Script argument was not" +
" a script or was not created by interpreted mode ");
}
return
callFunctionWithContinuations((
InterpretedFunction)
script,
scope,
ScriptRuntime.
emptyArgs);
}
/**
* Call function that may pause execution by capturing a continuation.
* Caller must be prepared to catch a ContinuationPending exception
* and resume execution by calling
* {@link #resumeContinuation(Object, Scriptable, Object)}.
* @param function The function to call. The function must have been
* compiled with interpreted mode (optimization level -1)
* @param scope The scope to execute the script against
* @param args The arguments for the function
* @throws ContinuationPending if the script calls a function that results
* in a call to {@link #captureContinuation()}
* @since 1.7 Release 2
*/
public
Object callFunctionWithContinuations(
Callable function,
Scriptable scope,
Object[]
args)
throws
ContinuationPending
{
if (!(
function instanceof
InterpretedFunction)) {
// Can only be applied to scripts
throw new
IllegalArgumentException("Function argument was not" +
" created by interpreted mode ");
}
if (
ScriptRuntime.
hasTopCall(this)) {
throw new
IllegalStateException("Cannot have any pending top " +
"calls when executing a script with continuations");
}
// Annotate so we can check later to ensure no java code in
// intervening frames
isContinuationsTopCall = true;
return
ScriptRuntime.
doTopCall(
function, this,
scope,
scope,
args,
isTopLevelStrict);
}
/**
* Capture a continuation from the current execution. The execution must
* have been started via a call to
* {@link #executeScriptWithContinuations(Script, Scriptable)} or
* {@link #callFunctionWithContinuations(Callable, Scriptable, Object[])}.
* This implies that the code calling
* this method must have been called as a function from the
* JavaScript script. Also, there cannot be any non-JavaScript code
* between the JavaScript frames (e.g., a call to eval()). The
* ContinuationPending exception returned must be thrown.
* @return A ContinuationPending exception that must be thrown
* @since 1.7 Release 2
*/
public
ContinuationPending captureContinuation() {
return new
ContinuationPending(
Interpreter.
captureContinuation(this));
}
/**
* Restarts execution of the JavaScript suspended at the call
* to {@link #captureContinuation()}. Execution of the code will resume
* with the functionResult as the result of the call that captured the
* continuation.
* Execution of the script will either conclude normally and the
* result returned, another continuation will be captured and
* thrown, or the script will terminate abnormally and throw an exception.
* @param continuation The value returned by
* {@link ContinuationPending#getContinuation()}
* @param functionResult This value will appear to the code being resumed
* as the result of the function that captured the continuation
* @throws ContinuationPending if another continuation is captured before
* the code terminates
* @since 1.7 Release 2
*/
public
Object resumeContinuation(
Object continuation,
Scriptable scope,
Object functionResult)
throws
ContinuationPending
{
Object[]
args = {
functionResult };
return
Interpreter.
restartContinuation(
(net.sourceforge.htmlunit.corejs.javascript.
NativeContinuation)
continuation,
this,
scope,
args);
}
/**
* Check whether a string is ready to be compiled.
* <p>
* stringIsCompilableUnit is intended to support interactive compilation of
* JavaScript. If compiling the string would result in an error
* that might be fixed by appending more source, this method
* returns false. In every other case, it returns true.
* <p>
* Interactive shells may accumulate source lines, using this
* method after each new line is appended to check whether the
* statement being entered is complete.
*
* @param source the source buffer to check
* @return whether the source is ready for compilation
* @since 1.4 Release 2
*/
public final boolean
stringIsCompilableUnit(
String source) {
boolean
errorseen = false;
CompilerEnvirons compilerEnv = new
CompilerEnvirons();
compilerEnv.
initFromContext(this);
// no source name or source text manager, because we're just
// going to throw away the result.
compilerEnv.
setGeneratingSource(false);
Parser p = new
Parser(
compilerEnv,
DefaultErrorReporter.
instance);
try {
p.
parse(
source, null, 1);
} catch (
EvaluatorException ee) {
errorseen = true;
}
// Return false only if an error occurred as a result of reading past
// the end of the file, i.e. if the source could be fixed by
// appending more source.
return !(
errorseen &&
p.
eof());
}
/**
* @deprecated
* @see #compileReader(Reader in, String sourceName, int lineno,
* Object securityDomain)
*/
@
Deprecated
public final
Script compileReader(
Scriptable scope,
Reader in,
String sourceName, int
lineno,
Object securityDomain)
throws
IOException
{
return
compileReader(
in,
sourceName,
lineno,
securityDomain);
}
/**
* Compiles the source in the given reader.
* <p>
* Returns a script that may later be executed.
* Will consume all the source in the reader.
*
* @param in the input reader
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number for reporting errors
* @param securityDomain an arbitrary object that specifies security
* information about the origin or owner of the script. For
* implementations that don't care about security, this value
* may be null.
* @return a script that may later be executed
* @exception IOException if an IOException was generated by the Reader
* @see net.sourceforge.htmlunit.corejs.javascript.Script
*/
public final
Script compileReader(
Reader in,
String sourceName,
int
lineno,
Object securityDomain)
throws
IOException
{
if (
lineno < 0) {
// For compatibility IllegalArgumentException can not be thrown here
lineno = 0;
}
return (
Script)
compileImpl(null,
in, null,
sourceName,
lineno,
securityDomain, false, null, null);
}
/**
* Compiles the source in the given string.
* <p>
* Returns a script that may later be executed.
*
* @param source the source string
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number for reporting errors. Use
* 0 if the line number is unknown.
* @param securityDomain an arbitrary object that specifies security
* information about the origin or owner of the script. For
* implementations that don't care about security, this value
* may be null.
* @return a script that may later be executed
* @see net.sourceforge.htmlunit.corejs.javascript.Script
*/
public final
Script compileString(
String source,
String sourceName, int
lineno,
Object securityDomain)
{
if (
lineno < 0) {
// For compatibility IllegalArgumentException can not be thrown here
lineno = 0;
}
return
compileString(
source, null, null,
sourceName,
lineno,
securityDomain);
}
protected
Script compileString(
String source,
Evaluator compiler,
ErrorReporter compilationErrorReporter,
String sourceName, int
lineno,
Object securityDomain)
{
try {
return (
Script)
compileImpl(null, null,
source,
sourceName,
lineno,
securityDomain, false,
compiler,
compilationErrorReporter);
} catch (
IOException ex) {
// Should not happen when dealing with source as string
throw new
RuntimeException();
}
}
/**
* Compile a JavaScript function.
* <p>
* The function source must be a function definition as defined by
* ECMA (e.g., "function f(a) { return a; }").
*
* @param scope the scope to compile relative to
* @param source the function definition source
* @param sourceName a string describing the source, such as a filename
* @param lineno the starting line number
* @param securityDomain an arbitrary object that specifies security
* information about the origin or owner of the script. For
* implementations that don't care about security, this value
* may be null.
* @return a Function that may later be called
* @see net.sourceforge.htmlunit.corejs.javascript.Function
*/
public final
Function compileFunction(
Scriptable scope,
String source,
String sourceName, int
lineno,
Object securityDomain)
{
return
compileFunction(
scope,
source, null, null,
sourceName,
lineno,
securityDomain);
}
protected
Function compileFunction(
Scriptable scope,
String source,
Evaluator compiler,
ErrorReporter compilationErrorReporter,
String sourceName, int
lineno,
Object securityDomain)
{
try {
return (
Function)
compileImpl(
scope, null,
source,
sourceName,
lineno,
securityDomain, true,
compiler,
compilationErrorReporter);
}
catch (
IOException ioe) {
// Should never happen because we just made the reader
// from a String
throw new
RuntimeException();
}
}
/**
* Decompile the script.
* <p>
* The canonical source of the script is returned.
*
* @param script the script to decompile
* @param indent the number of spaces to indent the result
* @return a string representing the script source
*/
public final
String decompileScript(
Script script, int
indent)
{
NativeFunction scriptImpl = (
NativeFunction)
script;
return
scriptImpl.
decompile(
indent, 0);
}
/**
* Decompile a JavaScript Function.
* <p>
* Decompiles a previously compiled JavaScript function object to
* canonical source.
* <p>
* Returns function body of '[native code]' if no decompilation
* information is available.
*
* @param fun the JavaScript function to decompile
* @param indent the number of spaces to indent the result
* @return a string representing the function source
*/
public final
String decompileFunction(
Function fun, int
indent)
{
if (
fun instanceof
BaseFunction)
return ((
BaseFunction)
fun).
decompile(
indent, 0);
return "function " +
fun.
getClassName() + "() {\n\t[native code]\n}\n";
}
/**
* Decompile the body of a JavaScript Function.
* <p>
* Decompiles the body a previously compiled JavaScript Function
* object to canonical source, omitting the function header and
* trailing brace.
*
* Returns '[native code]' if no decompilation information is available.
*
* @param fun the JavaScript function to decompile
* @param indent the number of spaces to indent the result
* @return a string representing the function body source.
*/
public final
String decompileFunctionBody(
Function fun, int
indent)
{
if (
fun instanceof
BaseFunction) {
BaseFunction bf = (
BaseFunction)
fun;
return
bf.
decompile(
indent,
Decompiler.
ONLY_BODY_FLAG);
}
// ALERT: not sure what the right response here is.
return "[native code]\n";
}
/**
* Create a new JavaScript object.
*
* Equivalent to evaluating "new Object()".
* @param scope the scope to search for the constructor and to evaluate
* against
* @return the new object
*/
public
Scriptable newObject(
Scriptable scope)
{
NativeObject result = new
NativeObject();
ScriptRuntime.
setBuiltinProtoAndParent(
result,
scope,
TopLevel.
Builtins.
Object);
return
result;
}
/**
* Create a new JavaScript object by executing the named constructor.
*
* The call <code>newObject(scope, "Foo")</code> is equivalent to
* evaluating "new Foo()".
*
* @param scope the scope to search for the constructor and to evaluate against
* @param constructorName the name of the constructor to call
* @return the new object
*/
public
Scriptable newObject(
Scriptable scope,
String constructorName)
{
return
newObject(
scope,
constructorName,
ScriptRuntime.
emptyArgs);
}
/**
* Creates a new JavaScript object by executing the named constructor.
*
* Searches <code>scope</code> for the named constructor, calls it with
* the given arguments, and returns the result.<p>
*
* The code
* <pre>
* Object[] args = { "a", "b" };
* newObject(scope, "Foo", args)</pre>
* is equivalent to evaluating "new Foo('a', 'b')", assuming that the Foo
* constructor has been defined in <code>scope</code>.
*
* @param scope The scope to search for the constructor and to evaluate
* against
* @param constructorName the name of the constructor to call
* @param args the array of arguments for the constructor
* @return the new object
*/
public
Scriptable newObject(
Scriptable scope,
String constructorName,
Object[]
args)
{
return
ScriptRuntime.
newObject(this,
scope,
constructorName,
args);
}
/**
* Create an array with a specified initial length.
* <p>
* @param scope the scope to create the object in
* @param length the initial length (JavaScript arrays may have
* additional properties added dynamically).
* @return the new array object
*/
public
Scriptable newArray(
Scriptable scope, int
length)
{
NativeArray result = new
NativeArray(
length);
ScriptRuntime.
setBuiltinProtoAndParent(
result,
scope,
TopLevel.
Builtins.
Array);
return
result;
}
/**
* Create an array with a set of initial elements.
*
* @param scope the scope to create the object in.
* @param elements the initial elements. Each object in this array
* must be an acceptable JavaScript type and type
* of array should be exactly Object[], not
* SomeObjectSubclass[].
* @return the new array object.
*/
public
Scriptable newArray(
Scriptable scope,
Object[]
elements)
{
if (
elements.
getClass().
getComponentType() !=
ScriptRuntime.
ObjectClass)
throw new
IllegalArgumentException();
NativeArray result = new
NativeArray(
elements);
ScriptRuntime.
setBuiltinProtoAndParent(
result,
scope,
TopLevel.
Builtins.
Array);
return
result;
}
/**
* Get the elements of a JavaScript array.
* <p>
* If the object defines a length property convertible to double number,
* then the number is converted Uint32 value as defined in Ecma 9.6
* and Java array of that size is allocated.
* The array is initialized with the values obtained by
* calling get() on object for each value of i in [0,length-1]. If
* there is not a defined value for a property the Undefined value
* is used to initialize the corresponding element in the array. The
* Java array is then returned.
* If the object doesn't define a length property or it is not a number,
* empty array is returned.
* @param object the JavaScript array or array-like object
* @return a Java array of objects
* @since 1.4 release 2
*/
public final
Object[]
getElements(
Scriptable object)
{
return
ScriptRuntime.
getArrayElements(
object);
}
/**
* Convert the value to a JavaScript boolean value.
* <p>
* See ECMA 9.2.
*
* @param value a JavaScript value
* @return the corresponding boolean value converted using
* the ECMA rules
*/
public static boolean
toBoolean(
Object value)
{
return
ScriptRuntime.
toBoolean(
value);
}
/**
* Convert the value to a JavaScript Number value.
* <p>
* Returns a Java double for the JavaScript Number.
* <p>
* See ECMA 9.3.
*
* @param value a JavaScript value
* @return the corresponding double value converted using
* the ECMA rules
*/
public static double
toNumber(
Object value)
{
return
ScriptRuntime.
toNumber(
value);
}
/**
* Convert the value to a JavaScript String value.
* <p>
* See ECMA 9.8.
* <p>
* @param value a JavaScript value
* @return the corresponding String value converted using
* the ECMA rules
*/
public static
String toString(
Object value)
{
return
ScriptRuntime.
toString(
value);
}
/**
* Convert the value to an JavaScript object value.
* <p>
* Note that a scope must be provided to look up the constructors
* for Number, Boolean, and String.
* <p>
* See ECMA 9.9.
* <p>
* Additionally, arbitrary Java objects and classes will be
* wrapped in a Scriptable object with its Java fields and methods
* reflected as JavaScript properties of the object.
*
* @param value any Java object
* @param scope global scope containing constructors for Number,
* Boolean, and String
* @return new JavaScript object
*/
public static
Scriptable toObject(
Object value,
Scriptable scope)
{
return
ScriptRuntime.
toObject(
scope,
value);
}
/**
* @deprecated
* @see #toObject(Object, Scriptable)
*/
@
Deprecated
public static
Scriptable toObject(
Object value,
Scriptable scope,
Class<?>
staticType)
{
return
ScriptRuntime.
toObject(
scope,
value);
}
/**
* Convenient method to convert java value to its closest representation
* in JavaScript.
* <p>
* If value is an instance of String, Number, Boolean, Function or
* Scriptable, it is returned as it and will be treated as the corresponding
* JavaScript type of string, number, boolean, function and object.
* <p>
* Note that for Number instances during any arithmetic operation in
* JavaScript the engine will always use the result of
* <tt>Number.doubleValue()</tt> resulting in a precision loss if
* the number can not fit into double.
* <p>
* If value is an instance of Character, it will be converted to string of
* length 1 and its JavaScript type will be string.
* <p>
* The rest of values will be wrapped as LiveConnect objects
* by calling {@link WrapFactory#wrap(Context cx, Scriptable scope,
* Object obj, Class staticType)} as in:
* <pre>
* Context cx = Context.getCurrentContext();
* return cx.getWrapFactory().wrap(cx, scope, value, null);
* </pre>
*
* @param value any Java object
* @param scope top scope object
* @return value suitable to pass to any API that takes JavaScript values.
*/
public static
Object javaToJS(
Object value,
Scriptable scope)
{
if (
value instanceof
String ||
value instanceof
Number
||
value instanceof
Boolean ||
value instanceof
Scriptable)
{
return
value;
} else if (
value instanceof
Character) {
return
String.
valueOf(((
Character)
value).
charValue());
} else {
Context cx =
Context.
getContext();
return
cx.
getWrapFactory().
wrap(
cx,
scope,
value, null);
}
}
/**
* Convert a JavaScript value into the desired type.
* Uses the semantics defined with LiveConnect3 and throws an
* Illegal argument exception if the conversion cannot be performed.
* @param value the JavaScript value to convert
* @param desiredType the Java type to convert to. Primitive Java
* types are represented using the TYPE fields in the corresponding
* wrapper class in java.lang.
* @return the converted value
* @throws EvaluatorException if the conversion cannot be performed
*/
public static
Object jsToJava(
Object value,
Class<?>
desiredType)
throws
EvaluatorException
{
return
NativeJavaObject.
coerceTypeImpl(
desiredType,
value);
}
/**
* @deprecated
* @see #jsToJava(Object, Class)
* @throws IllegalArgumentException if the conversion cannot be performed.
* Note that {@link #jsToJava(Object, Class)} throws
* {@link EvaluatorException} instead.
*/
@
Deprecated
public static
Object toType(
Object value,
Class<?>
desiredType)
throws
IllegalArgumentException
{
try {
return
jsToJava(
value,
desiredType);
} catch (
EvaluatorException ex) {
IllegalArgumentException
ex2 = new
IllegalArgumentException(
ex.
getMessage());
Kit.
initCause(
ex2,
ex);
throw
ex2;
}
}
/**
* Rethrow the exception wrapping it as the script runtime exception.
* Unless the exception is instance of {@link EcmaError} or
* {@link EvaluatorException} it will be wrapped as
* {@link WrappedException}, a subclass of {@link EvaluatorException}.
* The resulting exception object always contains
* source name and line number of script that triggered exception.
* <p>
* This method always throws an exception, its return value is provided
* only for convenience to allow a usage like:
* <pre>
* throw Context.throwAsScriptRuntimeEx(ex);
* </pre>
* to indicate that code after the method is unreachable.
* @throws EvaluatorException
* @throws EcmaError
*/
public static
RuntimeException throwAsScriptRuntimeEx(
Throwable e)
{
while ((
e instanceof
InvocationTargetException)) {
e = ((
InvocationTargetException)
e).
getTargetException();
}
// special handling of Error so scripts would not catch them
if (
e instanceof
Error) {
Context cx =
getContext();
if (
cx == null ||
!
cx.
hasFeature(
Context.
FEATURE_ENHANCED_JAVA_ACCESS))
{
throw (
Error)
e;
}
}
if (
e instanceof
RhinoException) {
throw (
RhinoException)
e;
}
throw new
WrappedException(
e);
}
/**
* Tell whether debug information is being generated.
* @since 1.3
*/
public final boolean
isGeneratingDebug()
{
return
generatingDebug;
}
/**
* Specify whether or not debug information should be generated.
* <p>
* Setting the generation of debug information on will set the
* optimization level to zero.
* @since 1.3
*/
public final void
setGeneratingDebug(boolean
generatingDebug)
{
if (
sealed)
onSealedMutation();
generatingDebugChanged = true;
if (
generatingDebug &&
getOptimizationLevel() > 0)
setOptimizationLevel(0);
this.
generatingDebug =
generatingDebug;
}
/**
* Tell whether source information is being generated.
* @since 1.3
*/
public final boolean
isGeneratingSource()
{
return
generatingSource;
}
/**
* Specify whether or not source information should be generated.
* <p>
* Without source information, evaluating the "toString" method
* on JavaScript functions produces only "[native code]" for
* the body of the function.
* Note that code generated without source is not fully ECMA
* conformant.
* @since 1.3
*/
public final void
setGeneratingSource(boolean
generatingSource)
{
if (
sealed)
onSealedMutation();
this.
generatingSource =
generatingSource;
}
/**
* Get the current optimization level.
* <p>
* The optimization level is expressed as an integer between -1 and
* 9.
* @since 1.3
*
*/
public final int
getOptimizationLevel()
{
return
optimizationLevel;
}
/**
* Set the current optimization level.
* <p>
* The optimization level is expected to be an integer between -1 and
* 9. Any negative values will be interpreted as -1, and any values
* greater than 9 will be interpreted as 9.
* An optimization level of -1 indicates that interpretive mode will
* always be used. Levels 0 through 9 indicate that class files may
* be generated. Higher optimization levels trade off compile time
* performance for runtime performance.
* The optimizer level can't be set greater than -1 if the optimizer
* package doesn't exist at run time.
* @param optimizationLevel an integer indicating the level of
* optimization to perform
* @since 1.3
*
*/
public final void
setOptimizationLevel(int
optimizationLevel)
{
if (
sealed)
onSealedMutation();
if (
optimizationLevel == -2) {
// To be compatible with Cocoon fork
optimizationLevel = -1;
}
checkOptimizationLevel(
optimizationLevel);
if (
codegenClass == null)
optimizationLevel = -1;
this.
optimizationLevel =
optimizationLevel;
}
public static boolean
isValidOptimizationLevel(int
optimizationLevel)
{
return -1 <=
optimizationLevel &&
optimizationLevel <= 9;
}
public static void
checkOptimizationLevel(int
optimizationLevel)
{
if (
isValidOptimizationLevel(
optimizationLevel)) {
return;
}
throw new
IllegalArgumentException(
"Optimization level outside [-1..9]: "+
optimizationLevel);
}
/**
* Returns the maximum stack depth (in terms of number of call frames)
* allowed in a single invocation of interpreter. If the set depth would be
* exceeded, the interpreter will throw an EvaluatorException in the script.
* Defaults to Integer.MAX_VALUE. The setting only has effect for
* interpreted functions (those compiled with optimization level set to -1).
* As the interpreter doesn't use the Java stack but rather manages its own
* stack in the heap memory, a runaway recursion in interpreted code would
* eventually consume all available memory and cause OutOfMemoryError
* instead of a StackOverflowError limited to only a single thread. This
* setting helps prevent such situations.
*
* @return The current maximum interpreter stack depth.
*/
public final int
getMaximumInterpreterStackDepth()
{
return
maximumInterpreterStackDepth;
}
/**
* Sets the maximum stack depth (in terms of number of call frames)
* allowed in a single invocation of interpreter. If the set depth would be
* exceeded, the interpreter will throw an EvaluatorException in the script.
* Defaults to Integer.MAX_VALUE. The setting only has effect for
* interpreted functions (those compiled with optimization level set to -1).
* As the interpreter doesn't use the Java stack but rather manages its own
* stack in the heap memory, a runaway recursion in interpreted code would
* eventually consume all available memory and cause OutOfMemoryError
* instead of a StackOverflowError limited to only a single thread. This
* setting helps prevent such situations.
*
* @param max the new maximum interpreter stack depth
* @throws IllegalStateException if this context's optimization level is not
* -1
* @throws IllegalArgumentException if the new depth is not at least 1
*/
public final void
setMaximumInterpreterStackDepth(int
max)
{
if(
sealed)
onSealedMutation();
if(
optimizationLevel != -1) {
throw new
IllegalStateException("Cannot set maximumInterpreterStackDepth when optimizationLevel != -1");
}
if(
max < 1) {
throw new
IllegalArgumentException("Cannot set maximumInterpreterStackDepth to less than 1");
}
maximumInterpreterStackDepth =
max;
}
/**
* Set the security controller for this context.
* <p> SecurityController may only be set if it is currently null
* and {@link SecurityController#hasGlobal()} is <tt>false</tt>.
* Otherwise a SecurityException is thrown.
* @param controller a SecurityController object
* @throws SecurityException if there is already a SecurityController
* object for this Context or globally installed.
* @see SecurityController#initGlobal(SecurityController controller)
* @see SecurityController#hasGlobal()
*/
public final void
setSecurityController(
SecurityController controller)
{
if (
sealed)
onSealedMutation();
if (
controller == null) throw new
IllegalArgumentException();
if (
securityController != null) {
throw new
SecurityException("Can not overwrite existing SecurityController object");
}
if (
SecurityController.
hasGlobal()) {
throw new
SecurityException("Can not overwrite existing global SecurityController object");
}
securityController =
controller;
}
/**
* Set the LiveConnect access filter for this context.
* <p> {@link ClassShutter} may only be set if it is currently null.
* Otherwise a SecurityException is thrown.
* @param shutter a ClassShutter object
* @throws SecurityException if there is already a ClassShutter
* object for this Context
*/
public synchronized final void
setClassShutter(
ClassShutter shutter)
{
if (
sealed)
onSealedMutation();
if (
shutter == null) throw new
IllegalArgumentException();
if (
hasClassShutter) {
throw new
SecurityException("Cannot overwrite existing " +
"ClassShutter object");
}
classShutter =
shutter;
hasClassShutter = true;
}
final synchronized
ClassShutter getClassShutter()
{
return
classShutter;
}
public interface
ClassShutterSetter {
public void
setClassShutter(
ClassShutter shutter);
public
ClassShutter getClassShutter();
}
public final synchronized
ClassShutterSetter getClassShutterSetter() {
if (
hasClassShutter)
return null;
hasClassShutter = true;
return new
ClassShutterSetter() {
@
Override
public void
setClassShutter(
ClassShutter shutter) {
classShutter =
shutter;
}
@
Override
public
ClassShutter getClassShutter() {
return
classShutter;
}
};
}
/**
* Get a value corresponding to a key.
* <p>
* Since the Context is associated with a thread it can be
* used to maintain values that can be later retrieved using
* the current thread.
* <p>
* Note that the values are maintained with the Context, so
* if the Context is disassociated from the thread the values
* cannot be retrieved. Also, if private data is to be maintained
* in this manner the key should be a java.lang.Object
* whose reference is not divulged to untrusted code.
* @param key the key used to lookup the value
* @return a value previously stored using putThreadLocal.
*/
public final
Object getThreadLocal(
Object key)
{
if (
threadLocalMap == null)
return null;
return
threadLocalMap.
get(
key);
}
/**
* Put a value that can later be retrieved using a given key.
* <p>
* @param key the key used to index the value
* @param value the value to save
*/
public synchronized final void
putThreadLocal(
Object key,
Object value)
{
if (
sealed)
onSealedMutation();
if (
threadLocalMap == null)
threadLocalMap = new
HashMap<
Object,
Object>();
threadLocalMap.
put(
key,
value);
}
/**
* Remove values from thread-local storage.
* @param key the key for the entry to remove.
* @since 1.5 release 2
*/
public final void
removeThreadLocal(
Object key)
{
if (
sealed)
onSealedMutation();
if (
threadLocalMap == null)
return;
threadLocalMap.
remove(
key);
}
/**
* @deprecated
* @see ClassCache#get(Scriptable)
* @see ClassCache#setCachingEnabled(boolean)
*/
@
Deprecated
public static void
setCachingEnabled(boolean
cachingEnabled)
{
}
/**
* Set a WrapFactory for this Context.
* <p>
* The WrapFactory allows custom object wrapping behavior for
* Java object manipulated with JavaScript.
* @see WrapFactory
* @since 1.5 Release 4
*/
public final void
setWrapFactory(
WrapFactory wrapFactory)
{
if (
sealed)
onSealedMutation();
if (
wrapFactory == null) throw new
IllegalArgumentException();
this.
wrapFactory =
wrapFactory;
}
/**
* Return the current WrapFactory, or null if none is defined.
* @see WrapFactory
* @since 1.5 Release 4
*/
public final
WrapFactory getWrapFactory()
{
if (
wrapFactory == null) {
wrapFactory = new
WrapFactory();
}
return
wrapFactory;
}
/**
* Return the current debugger.
* @return the debugger, or null if none is attached.
*/
public final
Debugger getDebugger()
{
return
debugger;
}
/**
* Return the debugger context data associated with current context.
* @return the debugger data, or null if debugger is not attached
*/
public final
Object getDebuggerContextData()
{
return
debuggerData;
}
/**
* Set the associated debugger.
* @param debugger the debugger to be used on callbacks from
* the engine.
* @param contextData arbitrary object that debugger can use to store
* per Context data.
*/
public final void
setDebugger(
Debugger debugger,
Object contextData)
{
if (
sealed)
onSealedMutation();
this.
debugger =
debugger;
debuggerData =
contextData;
}
/**
* Return DebuggableScript instance if any associated with the script.
* If callable supports DebuggableScript implementation, the method
* returns it. Otherwise null is returned.
*/
public static
DebuggableScript getDebuggableView(
Script script)
{
if (
script instanceof
NativeFunction) {
return ((
NativeFunction)
script).
getDebuggableView();
}
return null;
}
/**
* Controls certain aspects of script semantics.
* Should be overwritten to alter default behavior.
* <p>
* The default implementation calls
* {@link ContextFactory#hasFeature(Context cx, int featureIndex)}
* that allows to customize Context behavior without introducing
* Context subclasses. {@link ContextFactory} documentation gives
* an example of hasFeature implementation.
*
* @param featureIndex feature index to check
* @return true if the <code>featureIndex</code> feature is turned on
* @see #FEATURE_NON_ECMA_GET_YEAR
* @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME
* @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER
* @see #FEATURE_TO_STRING_AS_SOURCE
* @see #FEATURE_PARENT_PROTO_PROPRTIES
* @see #FEATURE_E4X
* @see #FEATURE_DYNAMIC_SCOPE
* @see #FEATURE_STRICT_VARS
* @see #FEATURE_STRICT_EVAL
* @see #FEATURE_LOCATION_INFORMATION_IN_ERROR
* @see #FEATURE_STRICT_MODE
* @see #FEATURE_WARNING_AS_ERROR
* @see #FEATURE_ENHANCED_JAVA_ACCESS
*/
public boolean
hasFeature(int
featureIndex)
{
ContextFactory f =
getFactory();
return
f.
hasFeature(this,
featureIndex);
}
/**
* Returns an object which specifies an E4X implementation to use within
* this <code>Context</code>. Note that the XMLLib.Factory interface should
* be considered experimental.
*
* The default implementation uses the implementation provided by this
* <code>Context</code>'s {@link ContextFactory}.
*
* @return An XMLLib.Factory. Should not return <code>null</code> if
* {@link #FEATURE_E4X} is enabled. See {@link #hasFeature}.
*/
public
XMLLib.
Factory getE4xImplementationFactory() {
return
getFactory().
getE4xImplementationFactory();
}
/**
* Get threshold of executed instructions counter that triggers call to
* <code>observeInstructionCount()</code>.
* When the threshold is zero, instruction counting is disabled,
* otherwise each time the run-time executes at least the threshold value
* of script instructions, <code>observeInstructionCount()</code> will
* be called.
*/
public final int
getInstructionObserverThreshold()
{
return
instructionThreshold;
}
/**
* Set threshold of executed instructions counter that triggers call to
* <code>observeInstructionCount()</code>.
* When the threshold is zero, instruction counting is disabled,
* otherwise each time the run-time executes at least the threshold value
* of script instructions, <code>observeInstructionCount()</code> will
* be called.<br>
* Note that the meaning of "instruction" is not guaranteed to be
* consistent between compiled and interpretive modes: executing a given
* script or function in the different modes will result in different
* instruction counts against the threshold.
* {@link #setGenerateObserverCount} is called with true if
* <code>threshold</code> is greater than zero, false otherwise.
* @param threshold The instruction threshold
*/
public final void
setInstructionObserverThreshold(int
threshold)
{
if (
sealed)
onSealedMutation();
if (
threshold < 0) throw new
IllegalArgumentException();
instructionThreshold =
threshold;
setGenerateObserverCount(
threshold > 0);
}
/**
* Turn on or off generation of code with callbacks to
* track the count of executed instructions.
* Currently only affects JVM byte code generation: this slows down the
* generated code, but code generated without the callbacks will not
* be counted toward instruction thresholds. Rhino's interpretive
* mode does instruction counting without inserting callbacks, so
* there is no requirement to compile code differently.
* @param generateObserverCount if true, generated code will contain
* calls to accumulate an estimate of the instructions executed.
*/
public void
setGenerateObserverCount(boolean
generateObserverCount) {
this.
generateObserverCount =
generateObserverCount;
}
/**
* Allow application to monitor counter of executed script instructions
* in Context subclasses.
* Run-time calls this when instruction counting is enabled and the counter
* reaches limit set by <code>setInstructionObserverThreshold()</code>.
* The method is useful to observe long running scripts and if necessary
* to terminate them.
* <p>
* The default implementation calls
* {@link ContextFactory#observeInstructionCount(Context cx,
* int instructionCount)}
* that allows to customize Context behavior without introducing
* Context subclasses.
*
* @param instructionCount amount of script instruction executed since
* last call to <code>observeInstructionCount</code>
* @throws Error to terminate the script
* @see #setOptimizationLevel(int)
*/
protected void
observeInstructionCount(int
instructionCount)
{
ContextFactory f =
getFactory();
f.
observeInstructionCount(this,
instructionCount);
}
/**
* Create class loader for generated classes.
* The method calls {@link ContextFactory#createClassLoader(ClassLoader)}
* using the result of {@link #getFactory()}.
*/
public
GeneratedClassLoader createClassLoader(
ClassLoader parent)
{
ContextFactory f =
getFactory();
return
f.
createClassLoader(
parent);
}
public final
ClassLoader getApplicationClassLoader()
{
if (
applicationClassLoader == null) {
ContextFactory f =
getFactory();
ClassLoader loader =
f.
getApplicationClassLoader();
if (
loader == null) {
ClassLoader threadLoader
=
Thread.
currentThread().
getContextClassLoader();
if (
threadLoader != null
&&
Kit.
testIfCanLoadRhinoClasses(
threadLoader))
{
// Thread.getContextClassLoader is not cached since
// its caching prevents it from GC which may lead to
// a memory leak and hides updates to
// Thread.getContextClassLoader
return
threadLoader;
}
// Thread.getContextClassLoader can not load Rhino classes,
// try to use the loader of ContextFactory or Context
// subclasses.
Class<?>
fClass =
f.
getClass();
if (
fClass !=
ScriptRuntime.
ContextFactoryClass) {
loader =
fClass.
getClassLoader();
} else {
loader =
getClass().
getClassLoader();
}
}
applicationClassLoader =
loader;
}
return
applicationClassLoader;
}
public final void
setApplicationClassLoader(
ClassLoader loader)
{
if (
sealed)
onSealedMutation();
if (
loader == null) {
// restore default behaviour
applicationClassLoader = null;
return;
}
if (!
Kit.
testIfCanLoadRhinoClasses(
loader)) {
throw new
IllegalArgumentException(
"Loader can not resolve Rhino classes");
}
applicationClassLoader =
loader;
}
/********** end of API **********/
/**
* Internal method that reports an error for missing calls to
* enter().
*/
static
Context getContext()
{
Context cx =
getCurrentContext();
if (
cx == null) {
throw new
RuntimeException(
"No Context associated with current Thread");
}
return
cx;
}
private
Object compileImpl(
Scriptable scope,
Reader sourceReader,
String sourceString,
String sourceName, int
lineno,
Object securityDomain, boolean
returnFunction,
Evaluator compiler,
ErrorReporter compilationErrorReporter)
throws
IOException
{
if(
sourceName == null) {
sourceName = "unnamed script";
}
if (
securityDomain != null &&
getSecurityController() == null) {
throw new
IllegalArgumentException(
"securityDomain should be null if setSecurityController() was never called");
}
// One of sourceReader or sourceString has to be null
if (!(
sourceReader == null ^
sourceString == null))
Kit.
codeBug();
// scope should be given if and only if compiling function
if (!(
scope == null ^
returnFunction))
Kit.
codeBug();
CompilerEnvirons compilerEnv = new
CompilerEnvirons();
compilerEnv.
initFromContext(this);
if (
compilationErrorReporter == null) {
compilationErrorReporter =
compilerEnv.
getErrorReporter();
}
if (
debugger != null) {
if (
sourceReader != null) {
sourceString =
Kit.
readReader(
sourceReader);
sourceReader = null;
}
}
Parser p = new
Parser(
compilerEnv,
compilationErrorReporter);
if (
returnFunction) {
p.
calledByCompileFunction = true;
}
if (
isStrictMode()) {
p.
setDefaultUseStrictDirective(true);
}
AstRoot ast;
if (
sourceString != null) {
ast =
p.
parse(
sourceString,
sourceName,
lineno);
} else {
ast =
p.
parse(
sourceReader,
sourceName,
lineno);
}
if (
returnFunction) {
// parser no longer adds function to script node
if (!(
ast.
getFirstChild() != null
&&
ast.
getFirstChild().
getType() ==
Token.
FUNCTION))
{
// XXX: the check just looks for the first child
// and allows for more nodes after it for compatibility
// with sources like function() {};;;
throw new
IllegalArgumentException(
"compileFunction only accepts source with single JS function: "+
sourceString);
}
}
IRFactory irf = new
IRFactory(
compilerEnv,
compilationErrorReporter);
ScriptNode tree =
irf.
transformTree(
ast);
// discard everything but the IR tree
p = null;
ast = null;
irf = null;
if (
compiler == null) {
compiler =
createCompiler();
}
Object bytecode =
compiler.
compile(
compilerEnv,
tree,
tree.
getEncodedSource(),
returnFunction);
if (
debugger != null) {
if (
sourceString == null)
Kit.
codeBug();
if (
bytecode instanceof
DebuggableScript) {
DebuggableScript dscript = (
DebuggableScript)
bytecode;
notifyDebugger_r(this,
dscript,
sourceString);
} else {
throw new
RuntimeException("NOT SUPPORTED");
}
}
Object result;
if (
returnFunction) {
result =
compiler.
createFunctionObject(this,
scope,
bytecode,
securityDomain);
} else {
result =
compiler.
createScriptObject(
bytecode,
securityDomain);
}
return
result;
}
private static void
notifyDebugger_r(
Context cx,
DebuggableScript dscript,
String debugSource)
{
cx.
debugger.
handleCompilationDone(
cx,
dscript,
debugSource);
for (int
i = 0;
i !=
dscript.
getFunctionCount(); ++
i) {
notifyDebugger_r(
cx,
dscript.
getFunction(
i),
debugSource);
}
}
private static
Class<?>
codegenClass =
Kit.
classOrNull(
"net.sourceforge.htmlunit.corejs.javascript.optimizer.Codegen");
private static
Class<?>
interpreterClass =
Kit.
classOrNull(
"net.sourceforge.htmlunit.corejs.javascript.Interpreter");
private
Evaluator createCompiler()
{
Evaluator result = null;
if (
optimizationLevel >= 0 &&
codegenClass != null) {
result = (
Evaluator)
Kit.
newInstanceOrNull(
codegenClass);
}
if (
result == null) {
result =
createInterpreter();
}
return
result;
}
static
Evaluator createInterpreter()
{
return (
Evaluator)
Kit.
newInstanceOrNull(
interpreterClass);
}
static
String getSourcePositionFromStack(int[]
linep)
{
Context cx =
getCurrentContext();
if (
cx == null)
return null;
if (
cx.
lastInterpreterFrame != null) {
Evaluator evaluator =
createInterpreter();
if (
evaluator != null)
return
evaluator.
getSourcePositionFromStack(
cx,
linep);
}
/**
* A bit of a hack, but the only way to get filename and line
* number from an enclosing frame.
*/
StackTraceElement[]
stackTrace = new
Throwable().
getStackTrace();
for (
StackTraceElement st :
stackTrace) {
String file =
st.
getFileName();
if (!(
file == null ||
file.
endsWith(".java"))) {
int
line =
st.
getLineNumber();
if (
line >= 0) {
linep[0] =
line;
return
file;
}
}
}
return null;
}
RegExpProxy getRegExpProxy()
{
if (
regExpProxy == null) {
Class<?>
cl =
Kit.
classOrNull(
"net.sourceforge.htmlunit.corejs.javascript.regexp.RegExpImpl");
if (
cl != null) {
regExpProxy = (
RegExpProxy)
Kit.
newInstanceOrNull(
cl);
}
}
return
regExpProxy;
}
final boolean
isVersionECMA1()
{
return
version ==
VERSION_DEFAULT ||
version >=
VERSION_1_3;
}
// The method must NOT be public or protected
SecurityController getSecurityController()
{
SecurityController global =
SecurityController.
global();
if (
global != null) {
return
global;
}
return
securityController;
}
public final boolean
isGeneratingDebugChanged()
{
return
generatingDebugChanged;
}
/**
* Add a name to the list of names forcing the creation of real
* activation objects for functions.
*
* @param name the name of the object to add to the list
*/
public void
addActivationName(
String name)
{
if (
sealed)
onSealedMutation();
if (
activationNames == null)
activationNames = new
HashSet<
String>();
activationNames.
add(
name);
}
/**
* Check whether the name is in the list of names of objects
* forcing the creation of activation objects.
*
* @param name the name of the object to test
*
* @return true if an function activation object is needed.
*/
public final boolean
isActivationNeeded(
String name)
{
return
activationNames != null &&
activationNames.
contains(
name);
}
/**
* Remove a name from the list of names forcing the creation of real
* activation objects for functions.
*
* @param name the name of the object to remove from the list
*/
public void
removeActivationName(
String name)
{
if (
sealed)
onSealedMutation();
if (
activationNames != null)
activationNames.
remove(
name);
}
public final boolean
isStrictMode() {
return
isTopLevelStrict || (
currentActivationCall != null &&
currentActivationCall.
isStrict);
}
private static
String implementationVersion;
private final
ContextFactory factory;
private boolean
sealed;
private
Object sealKey;
Scriptable topCallScope;
boolean
isContinuationsTopCall;
NativeCall currentActivationCall;
XMLLib cachedXMLLib;
BaseFunction typeErrorThrower;
// for Objects, Arrays to tag themselves as being printed out,
// so they don't print themselves out recursively.
// Use ObjToIntMap instead of java.util.HashSet for JDK 1.1 compatibility
ObjToIntMap iterating;
Object interpreterSecurityDomain;
int
version;
private
SecurityController securityController;
private boolean
hasClassShutter;
private
ClassShutter classShutter;
private
ErrorReporter errorReporter;
RegExpProxy regExpProxy;
private
Locale locale;
private boolean
generatingDebug;
private boolean
generatingDebugChanged;
private boolean
generatingSource=true;
boolean
useDynamicScope;
private int
optimizationLevel;
private int
maximumInterpreterStackDepth;
private
WrapFactory wrapFactory;
Debugger debugger;
private
Object debuggerData;
private int
enterCount;
private
Object propertyListeners;
private
Map<
Object,
Object>
threadLocalMap;
private
ClassLoader applicationClassLoader;
/**
* This is the list of names of objects forcing the creation of
* function activation records.
*/
Set<
String>
activationNames;
// For the interpreter to store the last frame for error reports etc.
Object lastInterpreterFrame;
// For the interpreter to store information about previous invocations
// interpreter invocations
ObjArray previousInterpreterInvocations;
// For instruction counting (interpreter only)
int
instructionCount;
int
instructionThreshold;
// It can be used to return the second index-like result from function
int
scratchIndex;
// It can be used to return the second uint32 result from function
long
scratchUint32;
// It can be used to return the second Scriptable result from function
Scriptable scratchScriptable;
// Generate an observer count on compiled code
public boolean
generateObserverCount = false;
boolean
isTopLevelStrict;
}