Confirmed users
156
edits
(12 intermediate revisions by 8 users not shown) | |||
Line 1: | Line 1: | ||
== Note - This article describes XPConnect circa 2007, and is woefully out of date == | |||
== Some notes on wrapped natives and native wrappers == | == Some notes on wrapped natives and native wrappers == | ||
Line 36: | Line 38: | ||
# The reference the <tt>JSObject</tt>'s private slot has to the <tt>XPCWrappedNative</tt>. | # The reference the <tt>JSObject</tt>'s private slot has to the <tt>XPCWrappedNative</tt>. | ||
This models a simple fact: for the <tt>XPCWrappedNative</tt> to die, both the C++ XPCOM world ''and'' the JS heap ought to have | This models a simple fact: for the <tt>XPCWrappedNative</tt> to die, both the C++ XPCOM world ''and'' the JS heap ought to have forgotten about it. If either side still sees it, it should stay alive. | ||
Thus the <tt>JSObject</tt> keeps the <tt>XPCWrappedNative</tt> alive, and the <tt>XPCWrappedNative</tt> keeps the <tt>nsISupports</tt> that it's pointing to alive. But there's an important detail: the <tt>XPCWrappedNative</tt> does ''not'' directly keep the <tt>JSObject</tt> alive. Doing so unconditionally -- for example rooting the <tt>JSObject</tt> -- would give rise to a cyclic graph: the pair of objects would keep each other alive indefinitely. So the <tt>XPCWrappedNative</tt> holds a pointer to the <tt>JSObject</tt>, but doesn't root it. | Thus the <tt>JSObject</tt> keeps the <tt>XPCWrappedNative</tt> alive, and the <tt>XPCWrappedNative</tt> keeps the <tt>nsISupports</tt> that it's pointing to alive. But there's an important detail: the <tt>XPCWrappedNative</tt> does ''not'' directly keep the <tt>JSObject</tt> alive. Doing so unconditionally -- for example rooting the <tt>JSObject</tt> -- would give rise to a cyclic graph: the pair of objects would keep each other alive indefinitely. So the <tt>XPCWrappedNative</tt> holds a pointer to the <tt>JSObject</tt>, but doesn't root it. | ||
Line 75: | Line 77: | ||
+------------------------------------------------------------+ | +------------------------------------------------------------+ | ||
The picture looks similar to the first one, and is similar in terms of memory ''ownership'' semantics. The functional difference is that a <tt>XPCNativeWrapper</tt> performs property lookup differently | The picture looks similar to the first one, and is similar in terms of memory ''ownership'' semantics. The functional difference is that a <tt>XPCNativeWrapper</tt> performs property lookup differently from a normal wrapped native. The difference is related to a certain type of attack vector involving property-name shadowing. (See [[mdc:Safely accessing content DOM from chrome]] and [[mdc:XPCNativeWrapper]].) | ||
For example, suppose chrome JS associated with some browser logic or extension were to walk the content DOM. Doing so involves acquiring wrappers to content DOM nodes and calling DOM methods on the <tt>JSObject</tt> side of those wrappers, but with chrome privilege. If content were malicious, it could shadow DOM methods that are supposed to pass through to the underlying C++ DOM element, by attaching same-named properties to the wrapper JSObject. If these shadowed properties contained attack code, chrome would call the attack code with chrome privilege. | For example, suppose chrome JS associated with some browser logic (to maintain, e.g., the current document's title in the window titlebar; or hover-sensitive context menu items) or extension were to walk the content DOM. Doing so involves acquiring wrappers to content DOM nodes and calling DOM methods on the <tt>JSObject</tt> side of those wrappers, but with chrome privilege. If content were malicious, it could shadow DOM methods that are supposed to pass through to the underlying C++ DOM element, by attaching same-named properties to the wrapper JSObject. If these shadowed properties contained attack code, and other bugs bit, chrome would call the attack code with chrome privilege. Even without other bugs, chrome could be misled by content properties. | ||
So instead of returning a normal wrapper to JS chrome in such a case, we return a wrapper whose <tt>JSObject</tt> has class <tt>XPCNativeWrapper</tt>. This wrapper still has an <tt>XPCWrappedNative</tt> object that points to the DOM node, but the <tt>JSObject</tt> in the cluster routes property-lookup requests through <tt>JSClass</tt> methods in <tt>XPCNativeWrapper.cpp</tt>. Such a wrapper performs special property lookup that's guaranteed to pass through to the underlying wrapped C++ object, and ignore script properties that might shadow native properties. | So instead of returning a normal wrapper to JS chrome in such a case, we return a wrapper whose <tt>JSObject</tt> has class <tt>XPCNativeWrapper</tt>. This wrapper still has an <tt>XPCWrappedNative</tt> object that points to the DOM node, but the <tt>JSObject</tt> in the cluster routes property-lookup requests through <tt>JSClass</tt> methods in <tt>XPCNativeWrapper.cpp</tt>. Such a wrapper performs special property lookup that's guaranteed to pass through to the underlying wrapped C++ object, and ignore script properties that might shadow native properties. | ||
Line 121: | Line 123: | ||
When such a DOM node is deleted, it releases all the "preserved wrappers" associated with it. Until then, properties added to the wrapper by script content persist. | When such a DOM node is deleted, it releases all the "preserved wrappers" associated with it. Until then, properties added to the wrapper by script content persist. | ||
=== Tearoffs === | |||
There are two uses of the term "tearoff" in XPCOM. | |||
The first use describes a space optimization in objects that nominally support quite a lot of interfaces, but in reality only ever have a smaller number of those interfaces used. In such cases it pays to move the less-often-used parts of the interface into auxiliary objects that are constructed on demand. These auxiliary objects are called "tearoffs". A tearoff "pretends" to share its identity with the base object it was "torn off" of, by maintaining a pointer to the base object. Any time someone attempts to QI the tearoff to its base type, the tearoff addref's the base object and returns the pointer to the base object it holds. Any time someone attempts to QI the base object for the tearoff type, the base object manufactures a tearoff (that points back to the base object) and returns it. A tearoff typically keeps its base object alive. The base object may also cache its tearoff, if it expects to reuse it, but this will typically create an ownership cycle. | |||
The second use describes a particular class in XPConnect called XPCWrappedNativeTearOff. Objects of this type are a speed optimization. They are manufactured by XPConnect in order to cache the results of a QI performed by script code manipulating a wrapped native. Suppose a script has a wrapped native base object: XPConnect thinks this base object is an nsISupports. Every time a script accesses it as an nsIFoo, XPConnect is obliged to QI the nsISupports to nsIFoo. This takes time. To avoid doing this on every call, XPConnect builds a an XPCWrappedNativeTearOff after the first QI and stores an XPCNativeInterface, a reflected JSObject, and the nsISupports returned from the QI (which may itself be a normal XPCOM tearoff, so is not necessarily the base nsISupports). The reflected JSObject in the tearoff is returned to the script, so that future calls through it will use the cached XPCNativeInterface in the XPCWrappedNativeTearOff, and not perform any new QI. |