|
|
| Line 1: |
Line 1: |
| See also http://www.christianwimmer.at/Publications/Wagner11a/Wagner11a.pdf
| | =REDIRECT [[sfink/Contexts and Compartments]] |
| | |
| == Contexts are Control, Compartments are Data ==
| |
| | |
| JSContexts are control, JSCompartments are data.
| |
| | |
| A JSContext (from here on, 'context') represents the execution of JS code, so
| |
| it contains a JS stack and is associated with a thread. A JSCompartment
| |
| ('compartment') is a memory space that objects and other garbage-collected
| |
| things (GCthings) are stored within.
| |
| | |
| A context is associated with ("is running inside") a single compartment
| |
| whenever it is doing anything. Any object created with that context will be
| |
| physically stored within its current compartment, and just about any GCthing it
| |
| reads or touches should also be within that compartment. To access data in
| |
| other compartments, the context must first "enter" that other compartment. This
| |
| is termed a "cross-compartment call" (remember, contexts are control). Usually,
| |
| the context will enter another compartment, do a bunch of stuff, and then exit
| |
| back to the original compartment -- though that's just because of how JS
| |
| control flow works. (There's a stack. When you make a function call, it'll
| |
| eventually finish and you'll return to what you were doing before the call.)
| |
| | |
| When a context is not running code (its JS stack is empty; it's not in a
| |
| request) then it isn't really associated with any compartment at all. In the
| |
| future, starting a request and entering an initial compartment will become the
| |
| same action. Also, a context is only ever running on one thread at a time.
| |
| | |
| In implementation terms, a context has a compartment field (cx->compartment)
| |
| that gives the current compartment, and a scope object (cx->globalObject) that
| |
| is one of the objects within the current compartment. In fact, cx->compartment
| |
| could considered a cached version of cx->globalObject->getCompartment().
| |
| Contexts also contain a "pending exception" object, which must also be in the
| |
| same compartment when set. New objects are created within the current
| |
| compartment, and their scope chains are initialized to cx->globalObject.
| |
| | |
| To make a cross-compartment call, cx->compartment is updated to the new
| |
| compartment and the scope object is set to a wrapper (really, a proxy) inside
| |
| the new compartment. The wrapper mediates access to the original scope object.
| |
| Also, a dummy frame that represents the compartment transition is pushed onto
| |
| the JS stack, because the security privileges of executing code are detemined
| |
| by the current stack. For example, if your chrome code calls a content script,
| |
| that script will execute with content privileges until it returns, then it will
| |
| revert to chrome privileges.
| |
| | |
| When debugging, it is helpful to know that a compartment is associated with a
| |
| "JSPrincipals" object that represents the "security information" for the
| |
| contents of that compartment. This is used to decide who can access what, and
| |
| is mostly opaque to the JS engine. But for Gecko, it'll typically contain a
| |
| human-understandable URL, which makes it much easier to figure out what's going
| |
| on.
| |
| | |
| Anything within a single compartment can freely access anything else in that
| |
| same compartment. No locking or wrappers are necessary (or possible). The
| |
| overall model is thus a partitioning of all (garbage collectible) data into
| |
| separate compartments, with controlled access from one compartment to another
| |
| but lockless, direct access between objects within a compartment.
| |
| Cross-compartment access is handled via "wrappers", which is the subject of the
| |
| next section.
| |
| | |
| == Wrappers ==
| |
| | |
| GCthings may be wrapped in cross-compartment wrappers for a number of reasons.
| |
| When a context is transitioning from one compartment to another (ie, it's
| |
| making a cross-compartment call), its scope object and pending exception (if
| |
| any) are changed to wrappers pointing back to the objects in the old
| |
| compartment. But any object can be wrapped in a cross-compartment wrapper if
| |
| needed. You can clone an object from another compartment, and all of its
| |
| properties will be wrappers pointing at the "real" properties in the origin
| |
| compartment.
| |
| | |
| Cross-compartment wrappers do not compose. When you wrap an object, any
| |
| existing wrappers will be ripped off first. (Slight oversimplification; there
| |
| is one exception.) In fact, the type of wrapper used for an object is uniquely
| |
| determined by the source and destination compartments.
| |
| | |
| The precise terminology is a little confusing. A cross-compartment wrapper is a
| |
| JSObject whose class is one of the proxy classes. When you access such an
| |
| object, it fetches its proxy handler (a subclass of JSProxyHandler) out of a
| |
| slot to decide how to handle that access. Confusingly, in the code a
| |
| JSCrossCompartmentWrapper is the subclass of JSProxyHandler that manages
| |
| cross-compartment access, but usually when we refer to a "cross-compartment
| |
| wrapper", we're really talking about the JSObject. (The JSObject of type
| |
| js::SomethingProxyClass that has a private JSSLOT_PROXY_HANDLER field
| |
| containing a JSProxyHandler subclass that knows how to mediate access to the
| |
| proxied object stored in JSSLOT_PROXY_PRIVATE. Phew.)
| |
| | |
| A proxy handler mediates access to the proxied objects based on a set of rules
| |
| embodied by some subclass of JSProxyHandler. A proxy handler might allow all
| |
| accesses through, conceal certain properties, or check on each access whether
| |
| the source compartment is allowed to see a particular property. Examples of
| |
| proxy handler classes are the things listed on
| |
| https://developer.mozilla.org/en/XPConnect_wrappers : cross-origin wrappers
| |
| (XOWs), chrome object wrappers (COWs), etc.
| |
| | |
| Also, the same wrapper will always be used for a given object. This is
| |
| necessary for equality testing between independently generated wrappings of the
| |
| same object, and useful for performance and memory usage as well. Internally,
| |
| every compartment has a wrapperCache that is keyed off of wrapped objects'
| |
| identity. You could think of the flavor of wrapper (i.e., the type of proxy
| |
| handler) being determined by the tuple <destination compartment, source
| |
| compartment, object>, but the object is stored within the source compartment so
| |
| those last two are redundant.
| |
| | |
| From the JS engine's point of view, there are a bunch of objects, every object
| |
| lives in a different compartment, and whenever you call something or point to
| |
| something in another compartment, the engine will interpose a cross-compartment
| |
| wrapper for you. It's up to the embedding -- the user of the JS engine -- to
| |
| decide how to divide up data into different compartments, and what the behavior
| |
| is triggered when you cross between compartments. You could have a "home"
| |
| compartment and a "bigger" compartment, and the cross-compartment wrapper could
| |
| convert any string to Pig Latin when it is retrieved from "bigger" by "home".
| |
| More practically, you could conceal certain properties from view when accessing
| |
| them from an "unprivileged" compartment (whatever that might mean in your
| |
| embedding), or you could do locking or queuing when accessing one compartment
| |
| from another compartment in a different thread. Or add a remoting layer.
| |
| | |
| XPConnect (Gecko's current SpiderMonkey embedding code) uses cross-compartment
| |
| wrappers to implement security policies and access rules. The 'Introduction'
| |
| section at https://developer.mozilla.org/en/XPConnect_security_membranes gives
| |
| a very good description of what XPConnect is using the wrappers for. Gecko uses
| |
| one compartment for chrome, and one compartment for each content domain. The
| |
| wrapper is chosen based on whether the two compartments are the same origin, or
| |
| whether one is privileged to see anything or a subset of the information in the
| |
| other, etc. See js/src/xpconnect/wrappers/WrapperFactory.cpp for the gruesome
| |
| details.
| |
| | |
| == Threads ==
| |
| | |
| A compartment can only be accessed by a single thread at a time, though it
| |
| doesn't have to be the same thread every time. Additionally, only one thread
| |
| may have stack frames running a given compartment's code at a time.
| |
| (Equivalently, "access compartment X" is defined as "currently executing or
| |
| potentially returning to a frame from compartment X").
| |
| | |
| A context is associated with a thread. (JSContext has a thread_ field with
| |
| thread-specific data.) Multiple contexts may be associated with the same
| |
| thread. A context is associated with a compartment whenever it is running code
| |
| (equivalently, when it is in a request). A context may move between
| |
| compartments, and have multiple compartments' code on its stack(s).
| |
| | |
| ...to be finished...
| |