1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7  package org.mozilla.javascript;
8 
9 import java.util.EnumMap;
10 
11 /**
12  * A top-level scope object that provides special means to cache and preserve
13  * the initial values of the built-in constructor properties for better
14  * ECMAScript compliance.
15  *
16  * <p>ECMA 262 requires that most constructors used internally construct
17  * objects with the original prototype object as value of their [[Prototype]]
18  * internal property. Since built-in global constructors are defined as
19  * writable and deletable, this means they should be cached to protect against
20  * redefinition at runtime.</p>
21  *
22  * <p>In order to implement this efficiently, this class provides a mechanism
23  * to access the original built-in global constructors and their prototypes
24  * via numeric class-ids. To make use of this, the new
25  * {@link ScriptRuntime#newBuiltinObject ScriptRuntime.newBuiltinObject} and
26  * {@link ScriptRuntime#setBuiltinProtoAndParent ScriptRuntime.setBuiltinProtoAndParent}
27  * methods should be used to create and initialize objects of built-in classes
28  * instead of their generic counterparts.</p>
29  *
30  * <p>Calling {@link org.mozilla.javascript.Context#initStandardObjects()}
31  * with an instance of this class as argument will automatically cache
32  * built-in classes after initialization. For other setups involving
33  * top-level scopes that inherit global properties from their proptotypes
34  * (e.g. with dynamic scopes) embeddings should explicitly call
35  * {@link #cacheBuiltins()} to initialize the class cache for each top-level
36  * scope.</p>
37  */
38 public class TopLevel extends IdScriptableObject {
39 
40     static final long serialVersionUID = -4648046356662472260L;
41 
42     /**
43      * An enumeration of built-in ECMAScript objects.
44      */
45     public enum Builtins {
46         /** The built-in Object type. */
47         Object,
48         /** The built-in Array type. */
49         Array,
50         /** The built-in Function type. */
51         Function,
52         /** The built-in String type. */
53         String,
54         /** The built-in Number type. */
55         Number,
56         /** The built-in Boolean type. */
57         Boolean,
58         /** The built-in RegExp type. */
59         RegExp,
60         /** The built-in Error type. */
61         Error
62     }
63 
64     private EnumMap<Builtins, BaseFunction> ctors;
65 
66     @Override
getClassName()67     public String getClassName() {
68         return "global";
69     }
70 
71     /**
72      * Cache the built-in ECMAScript objects to protect them against
73      * modifications by the script. This method is called automatically by
74      * {@link ScriptRuntime#initStandardObjects ScriptRuntime.initStandardObjects}
75      * if the scope argument is an instance of this class. It only has to be
76      * called by the embedding if a top-level scope is not initialized through
77      * <code>initStandardObjects()</code>.
78      */
cacheBuiltins()79     public void cacheBuiltins() {
80         ctors = new EnumMap<Builtins, BaseFunction>(Builtins.class);
81         for (Builtins builtin : Builtins.values()) {
82             Object value = ScriptableObject.getProperty(this, builtin.name());
83             if (value instanceof BaseFunction) {
84                 ctors.put(builtin, (BaseFunction)value);
85             }
86         }
87     }
88 
89     /**
90      * Static helper method to get a built-in object constructor with the given
91      * <code>type</code> from the given <code>scope</code>. If the scope is not
92      * an instance of this class or does have a cache of built-ins,
93      * the constructor is looked up via normal property lookup.
94      *
95      * @param cx the current Context
96      * @param scope the top-level scope
97      * @param type the built-in type
98      * @return the built-in constructor
99      */
getBuiltinCtor(Context cx, Scriptable scope, Builtins type)100     public static Function getBuiltinCtor(Context cx,
101                                           Scriptable scope,
102                                           Builtins type) {
103         // must be called with top level scope
104         assert scope.getParentScope() == null;
105         if (scope instanceof TopLevel) {
106             Function result = ((TopLevel)scope).getBuiltinCtor(type);
107             if (result != null) {
108                 return result;
109             }
110         }
111         // fall back to normal constructor lookup
112         return ScriptRuntime.getExistingCtor(cx, scope, type.name());
113     }
114 
115     /**
116      * Static helper method to get a built-in object prototype with the given
117      * <code>type</code> from the given <code>scope</code>. If the scope is not
118      * an instance of this class or does have a cache of built-ins,
119      * the prototype is looked up via normal property lookup.
120      *
121      * @param scope the top-level scope
122      * @param type the built-in type
123      * @return the built-in prototype
124      */
getBuiltinPrototype(Scriptable scope, Builtins type)125     public static Scriptable getBuiltinPrototype(Scriptable scope,
126                                                  Builtins type) {
127         // must be called with top level scope
128         assert scope.getParentScope() == null;
129         if (scope instanceof TopLevel) {
130             Scriptable result = ((TopLevel)scope)
131                     .getBuiltinPrototype(type);
132             if (result != null) {
133                 return result;
134             }
135         }
136         // fall back to normal prototype lookup
137         return ScriptableObject.getClassPrototype(scope, type.name());
138     }
139 
140     /**
141      * Get the cached built-in object constructor from this scope with the
142      * given <code>type</code>. Returns null if {@link #cacheBuiltins()} has not
143      * been called on this object.
144      * @param type the built-in type
145      * @return the built-in constructor
146      */
getBuiltinCtor(Builtins type)147     public BaseFunction getBuiltinCtor(Builtins type) {
148         return ctors != null ? ctors.get(type) : null;
149     }
150 
151     /**
152      * Get the cached built-in object prototype from this scope with the
153      * given <code>type</code>. Returns null if {@link #cacheBuiltins()} has not
154      * been called on this object.
155      * @param type the built-in type
156      * @return the built-in prototype
157      */
getBuiltinPrototype(Builtins type)158     public Scriptable getBuiltinPrototype(Builtins type) {
159         BaseFunction func = getBuiltinCtor(type);
160         Object proto = func != null ? func.getPrototypeProperty() : null;
161         return proto instanceof Scriptable ? (Scriptable) proto : null;
162     }
163 
164 }
165