Each half of a split object always has a pointer to the other half. Inner objects implement the
JSExtendedClass.outerObject hook, which returns a pointer to the corresponding outer object. Outer objects implement the
JSExtendedClass.innerObject hook. But the association is not fixed. An outer object may be associated with different inner objects at different times.
This feature is complicated. Programs other than Mozilla that embed SpiderMonkey should avoid using split objects.
Split objects were introduced to resolve a problem posed by the
window object. Three interrelated requirements on the
window object seemed to conflict.
windowobject is the global object for scripts in Web pages. Firefox has to maintain this for Web compatibility. For performance, access to global properties must be fast.
There is a glaring difference in the lifetime of the
windowobject as seen from two different angles. Suppose a script in page A, in tab TA, has a reference to the
windowobject of page B in another tab TB. The
windowobject for tab TB must persist as the user navigates from page to page in that tab. To the script in page A, the
windowmust appear to be a single object that moves from page to page. But each web page must load with fresh globals.
(The problem is even a bit subtler than this, since the reference may be direct or indirect. The script in A might have opened TB by calling
window.open, which returns a direct reference to TB's
Functionobject carries a reference to the
window, via the scope chain. To further complicate matters, A and B are in the same security domain, so security checks between them automatically succeed. This makes the problem resistant to a wrapper-based approach.)
Older versions of Firefox accomplished this by blowing away all
windowproperties every time a user navigated to another page, then reusing the
Security privileges are granted to JS code on the basis of the scope chain of the executing code. SpiderMonkey walks up the scope chain to the nearest object that has
JSPrincipalsattached. The end of the scope chain is the global object for the script. But what
JSPrincipalsshould be attached to a
windowobject? The principals of the page the window is currently displaying? But then the page that called
window.opencould use a
withstatement to inject that
windowinto its scope chain, gaining access to that window's privileges. (Note:
withdoes add an object to the scope chain, but it's a special
Withwrapper object that is skipped, or something, during the search described here. This security feature is not peculiar to split objects. ...And yet, there is special code in jsobj.h with a comment about fixing some kind of interaction between
withan split objects. Write that down, it'll be on the test.)
Split objects were the solution. The inner
window object is different for each page a browser window visits. It serves as the "globals" object and provides the
JSPrincipals for scripts on that page. Access to inner
window properties is fast. The outer
window object is the object returned by
window.open. It represents the window or tab itself and survives as the user navigates in that window or tab. The
JSClass.resolve hook ensures that properties of the inner object are visible via the outer object, if the running code has the right principals to access them. This privilege check may be slow.
This section describes split objects as a feature of the JSAPI. It catalogues (and tries to explain) the subtle ways in which split objects affect SpiderMonkey's behavior.
Split objects are only useful for implementing objects that will be on the scope chain of functions. SpiderMonkey assumes that inner and outer objects are dangerous in two different and complementary ways.
Inner objects are dangerous because they have fast property accessors that do not perform security checks. Therefore, no function may be allowed to see an inner object that has different
JSPrincipals. Apart from the security issue, if one page directly or indirectly gets a reference to another page's
window object, that
window object must appear to behave like the outer window and navigate from page to page. SpiderMonkey arranges this by not allowing JS code to see inner objects at all. To enforce this rule:
thiswould evaluate to an inner object, it evaluates to the corresponding outer object instead.
Outer objects are dangerous because their
JSPrincipals may change over time. Because a
Function inherits the
JSPrincipals of its lexical scope (specifically, the nearest principals-aware object in its scope chain), untrusted code must never be able to make an outer object appear in a
Function's scope chain. Again, SpiderMonkey enforces a slightly stronger rule: outer objects may never appear in a scope chain at all, except when put there by an explicit C-level JSAPI call (to
JS_SetParent or equivalent). (Several objects, such as
window.navigator, are intentionally parented to the outer window object using such APIs.) To enforce this rule:
- APIs that allow the caller to pass a scope object always check that object first and fail if any outer objects are on its scope chain.
- In the spirit of this rule,
JS_GetScopeChainshould always return an inner object. But there is a special case when
JS_GetScopeChainis called on a
JSContextin which no code is currently running. By convention, the context's global object is returned in this case. For consistency with the rule, if that global object is an outer object, SpiderMonkey returns the inner object instead.
Inner and outer objects are in certain other respects the same object:
Object.hasOwnPropertyis called on an inner object, and the named property is found on an outer object, that's considered close enough and
Note that none of the rules listed here affects ordinary property accesses. SpiderMonkey's split object support, by itself, does not cause inner object properties to appear on the outer object or vice versa. Split objects that need this behavior must implement it in custom
JSClass hooks. In the case of
window, each half has custom hooks.