/* -*- 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/. */ package org.mozilla.javascript; import java.util.EnumMap; /** * A top-level scope object that provides special means to cache and preserve * the initial values of the built-in constructor properties for better * ECMAScript compliance. * * <p>ECMA 262 requires that most constructors used internally construct * objects with the original prototype object as value of their [[Prototype]] * internal property. Since built-in global constructors are defined as * writable and deletable, this means they should be cached to protect against * redefinition at runtime.</p> * * <p>In order to implement this efficiently, this class provides a mechanism * to access the original built-in global constructors and their prototypes * via numeric class-ids. To make use of this, the new * {@link ScriptRuntime#newBuiltinObject ScriptRuntime.newBuiltinObject} and * {@link ScriptRuntime#setBuiltinProtoAndParent ScriptRuntime.setBuiltinProtoAndParent} * methods should be used to create and initialize objects of built-in classes * instead of their generic counterparts.</p> * * <p>Calling {@link org.mozilla.javascript.Context#initStandardObjects()} * with an instance of this class as argument will automatically cache * built-in classes after initialization. For other setups involving * top-level scopes that inherit global properties from their proptotypes * (e.g. with dynamic scopes) embeddings should explicitly call * {@link #cacheBuiltins()} to initialize the class cache for each top-level * scope.</p> */ public class TopLevel extends IdScriptableObject { static final long serialVersionUID = -4648046356662472260L; /** * An enumeration of built-in ECMAScript objects. */ public enum Builtins { /** The built-in Object type. */ Object, /** The built-in Array type. */ Array, /** The built-in Function type. */ Function, /** The built-in String type. */ String, /** The built-in Number type. */ Number, /** The built-in Boolean type. */ Boolean, /** The built-in RegExp type. */ RegExp, /** The built-in Error type. */ Error } /** * An enumeration of built-in native errors. [ECMAScript 5 - 15.11.6] */ enum NativeErrors { /** Basic Error */ Error, /** The native EvalError. */ EvalError, /** The native RangeError. */ RangeError, /** The native ReferenceError. */ ReferenceError, /** The native SyntaxError. */ SyntaxError, /** The native TypeError. */ TypeError, /** The native URIError. */ URIError, /** The native InternalError (non-standard). */ InternalError, /** The native JavaException (non-standard). */ JavaException } private EnumMap<Builtins, BaseFunction> ctors; private EnumMap<NativeErrors, BaseFunction> errors; @Override public String getClassName() { return "global"; } /** * Cache the built-in ECMAScript objects to protect them against * modifications by the script. This method is called automatically by * {@link ScriptRuntime#initStandardObjects ScriptRuntime.initStandardObjects} * if the scope argument is an instance of this class. It only has to be * called by the embedding if a top-level scope is not initialized through * <code>initStandardObjects()</code>. */ public void cacheBuiltins() { ctors = new EnumMap<Builtins, BaseFunction>(Builtins.class); for (Builtins builtin : Builtins.values()) { Object value = ScriptableObject.getProperty(this, builtin.name()); if (value instanceof BaseFunction) { ctors.put(builtin, (BaseFunction)value); } } errors = new EnumMap<NativeErrors, BaseFunction>(NativeErrors.class); for (NativeErrors error : NativeErrors.values()) { Object value = ScriptableObject.getProperty(this, error.name()); if (value instanceof BaseFunction) { errors.put(error, (BaseFunction)value); } } } /** * Static helper method to get a built-in object constructor with the given * <code>type</code> from the given <code>scope</code>. If the scope is not * an instance of this class or does have a cache of built-ins, * the constructor is looked up via normal property lookup. * * @param cx the current Context * @param scope the top-level scope * @param type the built-in type * @return the built-in constructor */ public static Function getBuiltinCtor(Context cx, Scriptable scope, Builtins type) { // must be called with top level scope assert scope.getParentScope() == null; if (scope instanceof TopLevel) { Function result = ((TopLevel)scope).getBuiltinCtor(type); if (result != null) { return result; } } // fall back to normal constructor lookup return ScriptRuntime.getExistingCtor(cx, scope, type.name()); } /** * Static helper method to get a native error constructor with the given * <code>type</code> from the given <code>scope</code>. If the scope is not * an instance of this class or does have a cache of native errors, * the constructor is looked up via normal property lookup. * * @param cx the current Context * @param scope the top-level scope * @param type the native error type * @return the native error constructor */ static Function getNativeErrorCtor(Context cx, Scriptable scope, NativeErrors type) { // must be called with top level scope assert scope.getParentScope() == null; if (scope instanceof TopLevel) { Function result = ((TopLevel)scope).getNativeErrorCtor(type); if (result != null) { return result; } } // fall back to normal constructor lookup return ScriptRuntime.getExistingCtor(cx, scope, type.name()); } /** * Static helper method to get a built-in object prototype with the given * <code>type</code> from the given <code>scope</code>. If the scope is not * an instance of this class or does have a cache of built-ins, * the prototype is looked up via normal property lookup. * * @param scope the top-level scope * @param type the built-in type * @return the built-in prototype */ public static Scriptable getBuiltinPrototype(Scriptable scope, Builtins type) { // must be called with top level scope assert scope.getParentScope() == null; if (scope instanceof TopLevel) { Scriptable result = ((TopLevel)scope) .getBuiltinPrototype(type); if (result != null) { return result; } } // fall back to normal prototype lookup return ScriptableObject.getClassPrototype(scope, type.name()); } /** * Get the cached built-in object constructor from this scope with the * given <code>type</code>. Returns null if {@link #cacheBuiltins()} has not * been called on this object. * @param type the built-in type * @return the built-in constructor */ public BaseFunction getBuiltinCtor(Builtins type) { return ctors != null ? ctors.get(type) : null; } /** * Get the cached native error constructor from this scope with the * given <code>type</code>. Returns null if {@link #cacheBuiltins()} has not * been called on this object. * @param type the native error type * @return the native error constructor */ BaseFunction getNativeErrorCtor(NativeErrors type) { return errors != null ? errors.get(type) : null; } /** * Get the cached built-in object prototype from this scope with the * given <code>type</code>. Returns null if {@link #cacheBuiltins()} has not * been called on this object. * @param type the built-in type * @return the built-in prototype */ public Scriptable getBuiltinPrototype(Builtins type) { BaseFunction func = getBuiltinCtor(type); Object proto = func != null ? func.getPrototypeProperty() : null; return proto instanceof Scriptable ? (Scriptable) proto : null; } }