Sfink/Contexts and Compartments: Difference between revisions

no edit summary
No edit summary
No edit summary
Line 7: Line 7:
JSContexts are control, JSCompartments are data.
JSContexts are control, JSCompartments are data.


A JSContext (from here on, 'context') represents the execution of JS code, so
A JSContext (from here on, just ''context'') represents the execution of JS code. A context contains a JS stack and is associated with a thread. (A thread may use multiple contexts, but a given context will only execute on a single thread at a time.)
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
A JSCompartment (''compartment'') is a memory space that objects and other garbage-collected things (''GCthings'') are stored within.
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
A context is associated with a single compartment at all times (not necessarily always the same one, but only ever one at a time). The context is often said to be "running inside" that compartment. Any object created with that context will be physically stored within the context's current compartment. Just about any GCthing read or touched by that context should also be within that same compartment.
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)
To access data in another compartment, a context must first "enter" that other compartment. This is termed a "cross-compartment call" -- remember, contexts are control, so changing a context's compartment is only meaningful if you're going to run code. The context will enter another compartment, do some stuff, then return, at which time it'll exit
that gives the current compartment, and a default scope object (cx->globalObject) that
back to the original compartment. (The APIs allow you to change to a different compartment and never change back, but that's almost certainly a bug and will quickly trigger an assertion in a debug build the first time you touch an object in a compartment that differs from your context's compartment.)
is one of the objects within the current compartment. 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 a scope object within
that same compartment. (The scope object *might* be cx->globalObject, but will
probably be something taken from the stack instead.)


To make a cross-compartment call, cx->compartment is updated to the new
When a context is not running code -- as in, its JS stack is empty and it is 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. '''Update:''' or perhaps we'll eliminate contexts altogether and just map from a thread to the relevant data.
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
In implementation terms, a context has a field (cx->compartment) that gives the current compartment. Contexts also maintain a default scope object (cx->globalObject) that
"JSPrincipals" object that represents the "security information" for the
is required to always be within the same compartment, and a "pending exception" object which, if set, will also be in the same compartment. Any object created using a context will be created inside the context's current compartment, and the object's scope chain will be initialized to a scope object within that same compartment. (That scope object ''might'' be cx->globalObject, but really that's just the ultimate fallback. Usually the scope object will be found via the stack instead.)
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
To make a cross-compartment call, cx->compartment is updated to the new compartment. The scope object must also be updated, and for that reason you must pass in a target object in the destination compartment. The scope object will be set to the target object's global object. (There's a hacky special case when you're using a JSScript for the target object, since they don't have global objects, but ignore that.) If an exception is pending, it will be set to a wrapper (really, a proxy) inside the new compartment. The wrapper mediates access to the original exception object that lives in the origin compartment.
same compartment. No locking or wrappers are necessary (or possible). The
 
overall model is thus a partitioning of all (garbage collectible) data into
Finally, a dummy frame that represents the compartment transition is pushed onto the JS stack. This frame is used for setting the scope object of anything created while executing within the new compartment. Also, the security privileges of executing code are determined by the current stack -- eg, if your chrome code in a chrome compartment calls a content script in a content compartment, that script will execute with content privileges until it returns, then will revert to chrome privileges.
separate compartments, with controlled access from one compartment to another
 
but lockless, direct access between objects within a compartment.
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:
Cross-compartment access is handled via "wrappers", which is the subject of the
 
next section.
  (gdb) p obj
  $1 = (JSObject *) 0x7fffbeef
  (gdb) p obj->compartment()
  $2 = (JSCompartment *) 0xbf5450
  (gdb) p obj->compartment()->principals()
  $3 = (JSPrincipals *) 0xc29860
  (gdb) p obj->compartment()->principals->codebase
  $4 = 0x7fffd120 "[System Principal]"
    ...or perhaps...
  $4 = 0x7fffd120 "http://angryhippos.com/accounts/"
 
Anything within a single compartment can freely and directly 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 ==
== Wrappers ==
Confirmed users
328

edits