|
|
Taxonomy of Cajita ValuesFor purposes of explaining the semantics of Cajita, we classify JavaScript values into one of the following categories: Primitive ValuesPrimitive values are unconditionally accessible from both Cajita and JavaScript. - numbers, strings, booleans.
- null and undefined. Note that typeof null === "object".
Tamed-only ObjectsTamed-only Objects are objects that Cajita code cannot create, but that can be made available to Cajita by taming. From Cajita's perspective, these are primitive objects not coded in Cajita. - Constructors. A tamed constructor is a function mentioning this, typically meant to be invoked with new, whose prototype property points at a prototypical object (defined below) whose constructor property points back at this constructor. Constructors are for expressing class-like inheritance, and so can also be invoked by a constructor representing a "subclass" in order to initialize their "superclass" portion of the object. Cajita code can only invoke constructors with new, which is fine since Cajita code cannot define "subclass" constructors anyway. To Cajita code, constructors are always frozen.
- Constructed objects. An object is a constructed object if it is not a function and the first prototypical object on its prototype chain is not Object.prototype or Array.prototype. The value of that prototypical object's constructor property is the direct constructor of the constructed object. A constructed object should only be accessible to Cajita code if its direct constructor has been tamed as a constructor. Cajita code can only see whitelisted properties of constructed objects, and cannot extend constructed objects with new properties.
The allowed constructors and constructed objects necessarily includes the primitive value wrappers: Numbers, Strings, and Booleans. Good code should never create wrappers, but defensive code must be prepared to receive wrappers as arguments. Cajita ObjectsCajita Objects are objects that Cajita code can create. These can also be made available by taming. - Simple-functions. A function not mentioning this. Such a function can be safely invoked as a function, as a constructor using new, or as a method. Cajita code implicitly postpones freezing a named simple-function until its first use or escaping occurrence, so that it can initialize static properties on it. Anonymous simple-functions are born frozen. Tamed simple-functions, whether named or not, are also immediately frozen on taming. Any static property on a tamed function (a simple-function or constructor) must by tamed as non-writable.
- Records. A record is a non-prototypical object (see below) that inherits directly from Object.prototype or from another record. Records do not need to be tamed as such to be considered records. Any object meeting these conditions will be considered a record. Cajita code can make records with object literal expressions ({foo: 3}), by new Object(), or by cajita.beget(parentRecord). To prevent confusion with prototypical objects, there must not be any accessible function F such that F.prototype is a record. Untamed records inheriting from records are problematic. If possible, innocent record inheritance should be rewritten to create the nested record by calling cajita.beget(parentRecord) explicitly.
- (Once 113048 is committed) A particular distinguished record is cajita.PseudoFunctionProto. Those records inheriting from it are considered pseudo-functions. Valija code views pseudo-functions as if they are functions, translating direct calls into calls to the pseudo-function's call or apply member, which should be simple-functions.
- Arrays. An array is an object inheriting directly from Array.prototype and with the special array behavior (such as length adjustment) as defined in the ES3 spec. For non-host objects, a reliable test of whether a specimen object is an array is ({}).toString.call(specimen) === '[object Array]'. Cajita code can make arrays with array literal expressions (['foo', 3]) or by new Array(elements...). Note that this constructor call has irregular behavior when given a single argument, so the array literal expression should always be preferred.
All properties of records or arrays that don't end in double underbar are visible to Cajita code. The attributes of the visible properties are not individually manipulable. Rather, a record or array as a whole is either frozen or not. If it is frozen, then it is not extensible and all its properties are non-writable, enumerable, and non-deletable. Otherwise, a record or array is extensible and all its visible properties are writable, enumerable, and deletable. Forbidden ValuesForbidden Values are JavaScript values that must not be made available to Cajita code. - The global object.
- Prototypical objects. By prototypical object, informally we mean the object used as the prototype in the classical inheritance pattern. An object is considered prototypical if the value of its constructor property is a function whose prototype property points back at it.
- Exophoric functions. These are non-constructor functions that mention this. They are typically used by uncajoled JavaScript as methods stored in prototypical objects.
- Functions not enumerated by a taming decision and their instances.
- Host objects. Because even the specified semantics of host objects is an unpredictable mess, we currently prevent Cajita code from ever obtaining access to a host object. However, for taming the browser DOM tree (without weak pointers or weak tables), this technique prevents the garbage collector from doing its job. We may relax this restriction until JavaScript provides weak tables.
Although prototypical objects are not directly accessible to Cajita code, the prototypical objects associated with constructors are needed to explain the semantics visible from Cajita. - A prototypical object associated with a constructor must either inherit directly from another prototypical object associated with a constructor, or it must be Object.prototype which happens to inherit from nothing.
- If D.prototype directly inherits from B.prototype, where D and B are constructors, then B is D's super-constructor.
- Those properties of a prototypical object made accessible by a taming decision (see NiceNeighbor) are visibly inherited by the constructed objects which actually inherit from it.
- These indirectly observable properties must not change, as if all prototypical objects were frozen.
Toxic ValuesToxic Values are JavaScript values that must never be encountered by the Cajita runtime. - Ill-formed prototype chains. Note that taming is too late to prevent this. Safest would be not to allow such cases into the frame.
- Genuine arguments objects. The object created by the JavaScript expression arguments has bizarre semantics. It may or may not inherits from Array.prototype, it acts in some array-like ways but is not an array, it is typically joined to the parameter variables of the function that created it, and there is no test that can reliably determine whether a given object is an arguments object.
- Non-arrays that inherit (directly or indirectly) from Array.prototype.
- Non-functions that inherit (directly or indirectly) from Function.prototype.
Most of this taxonomy should work as stated inter-frame, but this is an unsupported experimental use largely untested at this time. InvariantsThere are some invariants implied by the above set of constraints. One raised in issue 1052 is: Among values visible to Cajita, only records can inherit from non-frozen objects, and these non-frozen parent objects must be records. Why is this? - A Cajita visible value must either be primitive, a tamed-only object, or a cajita object.
- Primitive values in JavaScript are implicitly frozen and inherit from nothing.
- Tamed only objects are either constructors or constructed objects.
- Constructors are functions which therefore inherit directly from Function.prototype which is treated as frozen and forbidden.
- Constructed objects inherit directly from their constructor's prototypical object, which is treated as frozen and forbidden and inherits from the super-constructor's prototypical object, etc, until we reach Object.prototype which is treated as frozen and forbidden.
- Cajita objects are simple functions, records, or arrays.
- simple functions inherit directly from Function.prototype which is treated as frozen and forbidden.
- Arrays inherit directly from Array.prototype which is treated as frozen and forbidden.
- Our only remaining case, records, may inherit from other records, which may or may not be frozen, or it may inherit directly from Object.prototype which is treated as frozen and forbidden.
|