Bug 475864 - Move native anonymous content checks into a wrapper so that quickstubs don't sidestep them. r=jst sr=bzbarsky
authorBlake Kaplan <mrbkap@gmail.com>
Thu, 23 Apr 2009 00:21:22 -0700
changeset 27664 da473f63b7edd2b552032f0843d0e9ad043b5b64
parent 27663 1a1611bb10630cda771042ef8089b7b6060c3f55
child 27665 60980742d9da494252ab77f7b5ed8fb60c51574f
push id6681
push usermrbkap@mozilla.com
push dateThu, 23 Apr 2009 07:21:38 +0000
treeherdermozilla-central@60980742d9da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, bzbarsky
bugs475864
milestone1.9.2a1pre
Bug 475864 - Move native anonymous content checks into a wrapper so that quickstubs don't sidestep them. r=jst sr=bzbarsky
caps/src/nsScriptSecurityManager.cpp
content/base/public/nsIContent.h
content/base/public/nsINode.h
content/base/src/nsGenericElement.cpp
dom/base/nsDOMClassInfo.cpp
js/src/xpconnect/idl/nsIXPCScriptable.idl
js/src/xpconnect/idl/nsIXPConnect.idl
js/src/xpconnect/src/Makefile.in
js/src/xpconnect/src/XPCCrossOriginWrapper.cpp
js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp
js/src/xpconnect/src/XPCSystemOnlyWrapper.cpp
js/src/xpconnect/src/XPCWrapper.cpp
js/src/xpconnect/src/XPCWrapper.h
js/src/xpconnect/src/nsXPConnect.cpp
js/src/xpconnect/src/xpcconvert.cpp
js/src/xpconnect/src/xpcprivate.h
js/src/xpconnect/src/xpcquickstubs.cpp
js/src/xpconnect/src/xpcwrappedjsclass.cpp
js/src/xpconnect/src/xpcwrappednative.cpp
testing/mochitest/MochiKit/Base.js
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -245,21 +245,16 @@ public:
         return mFlags;
     }
 
     PRBool IsDOMClass()
     {
         return !!(GetFlags() & nsIClassInfo::DOM_OBJECT);
     }
 
-    PRBool IsContentNode()
-    {
-        return !!(GetFlags() & nsIClassInfo::CONTENT_NODE);
-    }
-
     const char* GetName()
     {
         if (!mName) {
             if (mClassInfo) {
                 mClassInfo->GetClassDescription(&mName);
             }
 
             if (mName) {
@@ -739,25 +734,16 @@ nsScriptSecurityManager::CheckPropertyAc
         PRBool capabilityEnabled = PR_FALSE;
         rv = IsCapabilityEnabled(securityLevel.capability, &capabilityEnabled);
         if (NS_FAILED(rv) || !capabilityEnabled)
             rv = NS_ERROR_DOM_SECURITY_ERR;
         else
             rv = NS_OK;
     }
 
-    if (NS_SUCCEEDED(rv) && classInfoData.IsContentNode())
-    {
-        // No access to anonymous content from the web!  (bug 164086)
-        nsIContent *content = static_cast<nsIContent*>(aObj);
-        if (content->IsInNativeAnonymousSubtree()) {
-            rv = NS_ERROR_DOM_SECURITY_ERR;
-        }
-    }
-
     if (NS_SUCCEEDED(rv))
     {
 #ifdef DEBUG_CAPS_CheckPropertyAccessImpl
     printf(" GRANTED.\n");
 #endif
         return rv;
     }
 
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -169,39 +169,16 @@ public:
 
   /**
    * Returns |this| if it is not native anonymous, otherwise
    * first non native anonymous ancestor.
    */
   virtual nsIContent* FindFirstNonNativeAnonymous() const;
 
   /**
-   * Returns PR_TRUE if |this| or any of its ancestors is native anonymous.
-   */
-  PRBool IsInNativeAnonymousSubtree() const
-  {
-#ifdef DEBUG
-    if (HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE)) {
-      return PR_TRUE;
-    }
-    nsIContent* content = GetBindingParent();
-    while (content) {
-      if (content->IsRootOfNativeAnonymousSubtree()) {
-        NS_ERROR("Element not marked to be in native anonymous subtree!");
-        break;
-      }
-      content = content->GetBindingParent();
-    }
-    return PR_FALSE;
-#else
-    return HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE);
-#endif
-  }
-
-  /**
    * Returns true if and only if this node has a parent, but is not in
    * its parent's child list.
    */
   PRBool IsRootOfAnonymousSubtree() const
   {
     NS_ASSERTION(!IsRootOfNativeAnonymousSubtree() ||
                  (GetParent() && GetBindingParent() == GetParent()),
                  "root of native anonymous subtree must have parent equal "
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -766,16 +766,32 @@ public:
 #ifdef _IMPL_NS_LAYOUT
     return IsEditableInternal();
 #else
     return IsEditableExternal();
 #endif
   }
 
   /**
+   * Returns PR_TRUE if |this| or any of its ancestors is native anonymous.
+   */
+  PRBool IsInNativeAnonymousSubtree() const
+  {
+#ifdef DEBUG
+    if (HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE)) {
+      return PR_TRUE;
+    }
+    CheckNotNativeAnonymous();
+    return PR_FALSE;
+#else
+    return HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE);
+#endif
+  }
+
+  /**
    * Get the root content of an editor. So, this node must be a descendant of
    * an editor. Note that this should be only used for getting input or textarea
    * editor's root content. This method doesn't support HTML editors.
    */
   nsIContent* GetTextEditorRootContent(nsIEditor** aEditor = nsnull);
 
   /**
    * Get the nearest selection root, ie. the node that will be selected if the
@@ -887,16 +903,22 @@ protected:
   }
 
   PRBool IsEditableInternal() const;
   virtual PRBool IsEditableExternal() const
   {
     return IsEditableInternal();
   }
 
+#ifdef DEBUG
+  // Note: virtual so that IsInNativeAnonymousSubtree can be called accross
+  // module boundaries.
+  virtual void CheckNotNativeAnonymous() const;
+#endif
+
   nsresult GetParentNode(nsIDOMNode** aParentNode);
   nsresult GetChildNodes(nsIDOMNodeList** aChildNodes);
   nsresult GetFirstChild(nsIDOMNode** aFirstChild);
   nsresult GetLastChild(nsIDOMNode** aLastChild);
   nsresult GetPreviousSibling(nsIDOMNode** aPrevSibling);
   nsresult GetNextSibling(nsIDOMNode** aNextSibling);
   nsresult GetOwnerDocument(nsIDOMDocument** aOwnerDocument);
 
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -1,9 +1,10 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=79: */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
@@ -441,16 +442,33 @@ nsINode::GetChildNodesList()
     if (slots->mChildNodes) {
       NS_ADDREF(slots->mChildNodes);
     }
   }
 
   return slots->mChildNodes;
 }
 
+#ifdef DEBUG
+void
+nsINode::CheckNotNativeAnonymous() const
+{
+  if (!IsNodeOfType(eCONTENT))
+    return;
+  nsIContent* content = static_cast<const nsIContent *>(this)->GetBindingParent();
+  while (content) {
+    if (content->IsRootOfNativeAnonymousSubtree()) {
+      NS_ERROR("Element not marked to be in native anonymous subtree!");
+      break;
+    }
+    content = content->GetBindingParent();
+  }
+}
+#endif
+
 nsresult
 nsINode::GetParentNode(nsIDOMNode** aParentNode)
 {
   *aParentNode = nsnull;
 
   nsINode *parent = GetNodeParent();
 
   return parent ? CallQueryInterface(parent, aParentNode) : NS_OK;
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -6917,17 +6917,18 @@ nsNodeSH::PreCreate(nsISupports *nativeO
   nsIDocument* doc = node->GetOwnerDoc();
 
   if (!doc) {
     // No document reachable from nativeObj, use the global object
     // that was passed to this method.
 
     *parentObj = globalObj;
 
-    return NS_OK;
+    return node->IsInNativeAnonymousSubtree() ?
+      NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK;
   }
 
   // If we have a document, make sure one of these is true
   // (1) it has a script handling object,
   // (2) has had one, or has been marked to have had one,
   // (3) we are running a privileged script.
   // Event handling is possible only if (1). If (2) event handling is prevented.
   // If document has never had a script handling object,
@@ -6978,42 +6979,46 @@ nsNodeSH::PreCreate(nsISupports *nativeO
     native_parent = doc->GetScopeObject();
 
     if (!native_parent) {
       // No global object reachable from this document, use the
       // global object that was passed to this method.
 
       *parentObj = globalObj;
 
-      return NS_OK;
+      return node->IsInNativeAnonymousSubtree() ?
+        NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK;
     }
   }
 
   // XXXjst: Maybe we need to find the global to use from the
   // nsIScriptGlobalObject that's reachable from the node we're about
   // to wrap here? But that's not always reachable, let's use
   // globalObj for now...
 
   nsIXPConnectJSObjectHolder *wrapper;
   if (native_parent == doc &&
       (wrapper = static_cast<nsIXPConnectJSObjectHolder*>(doc->GetWrapper()))) {
     wrapper->GetJSObject(parentObj);
     if(*parentObj) {
-      return NS_OK;
+      return node->IsInNativeAnonymousSubtree() ?
+        NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK;
     }
   }
 
   jsval v;
   nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
   nsresult rv = WrapNative(cx, globalObj, native_parent, &v,
                            getter_AddRefs(holder));
+  NS_ENSURE_SUCCESS(rv, rv);
 
   *parentObj = JSVAL_TO_OBJECT(v);
 
-  return rv;
+  return node->IsInNativeAnonymousSubtree() ?
+    NS_SUCCESS_CHROME_ACCESS_ONLY : NS_OK;
 }
 
 NS_IMETHODIMP
 nsNodeSH::AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                       JSObject *obj, jsval id, jsval *vp, PRBool *_retval)
 {
   nsDOMClassInfo::PreserveNodeWrapper(wrapper);
   return nsEventReceiverSH::AddProperty(wrapper, cx, obj, id, vp, _retval);
--- a/js/src/xpconnect/idl/nsIXPCScriptable.idl
+++ b/js/src/xpconnect/idl/nsIXPCScriptable.idl
@@ -41,16 +41,18 @@
 #include "nsISupports.idl"
 #include "nsIXPConnect.idl"
 
 [ptr] native JSTracerPtr(JSTracer);
 
 %{ C++
 #define NS_SUCCESS_I_DID_SOMETHING \
    (NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_XPCONNECT,1))
+#define NS_SUCCESS_CHROME_ACCESS_ONLY \
+   (NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_XPCONNECT,2))
 %}
 
 /**
  * Note: This is not really an XPCOM interface.  For example, callers must
  * guarantee that they set the *_retval of the various methods that return a
  * boolean to PR_TRUE before making the call.  Implementations may skip writing
  * to *_retval unless they want to return PR_FALSE.
  */
--- a/js/src/xpconnect/idl/nsIXPConnect.idl
+++ b/js/src/xpconnect/idl/nsIXPConnect.idl
@@ -786,16 +786,18 @@ interface nsIXPConnect : nsISupports
         in PRUint32 interfaceCount,
         [array, size_is(interfaceCount)] in nsIIDPtr interfaceArray);
 
     /**
      * Returns a XPCNativeWrapper, XPCSafeJSObjectWrapper, or
      * XPCCrossOriginWrapper for the given object based on the principal, scope,
      * and filename flags that are passed in.
      *
+     * Note: In C++, the out jsval parameter must already be a strong GC root.
+     *
      * @param aJSContext
      *     A JSContext.
      * @param aObject
      *     The object to wrap.
      * @param aScope
      *     The scope to be used in the event that we create a
      *     XPCCrossOriginWrapper. Can be null.
      * @param aPrincipal
--- a/js/src/xpconnect/src/Makefile.in
+++ b/js/src/xpconnect/src/Makefile.in
@@ -104,16 +104,17 @@ CPPSRCS		= \
 		xpcwrappednativeinfo.cpp \
 		xpcwrappednativejsops.cpp \
 		xpcwrappednativeproto.cpp \
 		xpcwrappednativescope.cpp \
 		XPCNativeWrapper.cpp \
 		xpcJSWeakReference.cpp \
 		XPCSafeJSObjectWrapper.cpp \
 		XPCCrossOriginWrapper.cpp \
+		XPCSystemOnlyWrapper.cpp \
 		XPCWrapper.cpp \
 		xpcquickstubs.cpp \
 		dom_quickstubs.cpp \
 		$(NULL)
 ifdef XPC_IDISPATCH_SUPPORT
 CPPSRCS +=	XPCDispObject.cpp	\
 		XPCDispInterface.cpp	\
 		XPCDispConvert.cpp \
--- a/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp
+++ b/js/src/xpconnect/src/XPCCrossOriginWrapper.cpp
@@ -159,31 +159,17 @@ GetWrapper(JSObject *obj)
 
   return obj;
 }
 
 static inline
 JSObject *
 GetWrappedObject(JSContext *cx, JSObject *wrapper)
 {
-  if (STOBJ_GET_CLASS(wrapper) != &sXPC_XOW_JSClass.base) {
-    return nsnull;
-  }
-
-  jsval v;
-  if (!JS_GetReservedSlot(cx, wrapper, XPCWrapper::sWrappedObjSlot, &v)) {
-    JS_ClearPendingException(cx);
-    return nsnull;
-  }
-
-  if (!JSVAL_IS_OBJECT(v)) {
-    return nsnull;
-  }
-
-  return JSVAL_TO_OBJECT(v);
+  return XPCWrapper::UnwrapGeneric(cx, &sXPC_XOW_JSClass, wrapper);
 }
 
 JSBool
 XPC_XOW_WrapperMoved(JSContext *cx, XPCWrappedNative *innerObj,
                      XPCWrappedNativeScope *newScope)
 {
   typedef WrappedNative2WrapperMap::Link Link;
   XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
@@ -338,17 +324,18 @@ XPC_XOW_FunctionWrapper(JSContext *cx, J
       return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
     }
   } else {
     wrappedObj = obj;
   }
 
   JSObject *funObj = JSVAL_TO_OBJECT(argv[-2]);
   jsval funToCall;
-  if (!JS_GetReservedSlot(cx, funObj, 0, &funToCall)) {
+  if (!JS_GetReservedSlot(cx, funObj, XPCWrapper::eWrappedFunctionSlot,
+                          &funToCall)) {
     return JS_FALSE;
   }
 
   JSFunction *fun = JS_ValueToFunction(cx, funToCall);
   if (!fun) {
     return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
   }
 
@@ -428,18 +415,20 @@ XPC_XOW_WrapFunction(JSContext *cx, JSOb
                    JS_GetFunctionName(wrappedFun));
   if (!funWrapper) {
     return JS_FALSE;
   }
 
   JSObject *funWrapperObj = JS_GetFunctionObject(funWrapper);
   *rval = OBJECT_TO_JSVAL(funWrapperObj);
 
-  if (!JS_SetReservedSlot(cx, funWrapperObj, XPCWrapper::eXOWWrappedFunctionSlot, funobjVal) ||
-      !JS_SetReservedSlot(cx, funWrapperObj, XPCWrapper::eAllAccessSlot, JSVAL_FALSE)) {
+  if (!JS_SetReservedSlot(cx, funWrapperObj, XPCWrapper::eWrappedFunctionSlot,
+                          funobjVal) ||
+      !JS_SetReservedSlot(cx, funWrapperObj, XPCWrapper::eAllAccessSlot,
+                          JSVAL_FALSE)) {
     return JS_FALSE;
   }
 
   return JS_TRUE;
 }
 
 JSBool
 XPC_XOW_RewrapIfNeeded(JSContext *cx, JSObject *outerObj, jsval *vp)
@@ -530,18 +519,18 @@ XPC_XOW_WrapObject(JSContext *cx, JSObje
   // same scope as the one that is calling us.
   outerObj = JS_NewObjectWithGivenProto(cx, &sXPC_XOW_JSClass.base, nsnull,
                                         parent);
   if (!outerObj) {
     return JS_FALSE;
   }
 
   if (!JS_SetReservedSlot(cx, outerObj, XPCWrapper::sWrappedObjSlot, *vp) ||
-      !JS_SetReservedSlot(cx, outerObj, XPCWrapper::sResolvingSlot,
-                          JSVAL_FALSE) ||
+      !JS_SetReservedSlot(cx, outerObj, XPCWrapper::sFlagsSlot,
+                          JSVAL_ZERO) ||
       !JS_SetReservedSlot(cx, outerObj, XPC_XOW_ScopeSlot,
                           PRIVATE_TO_JSVAL(parentScope))) {
     return JS_FALSE;
   }
 
   *vp = OBJECT_TO_JSVAL(outerObj);
 
   map->Add(wn->GetScope()->GetWrapperMap(), wrappedObj, outerObj);
@@ -552,21 +541,21 @@ XPC_XOW_WrapObject(JSContext *cx, JSObje
 static JSBool
 XPC_XOW_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   // All AddProperty needs to do is pass on addProperty requests to
   // same-origin objects, and throw for all else.
 
   obj = GetWrapper(obj);
   jsval resolving;
-  if (!JS_GetReservedSlot(cx, obj, XPCWrapper::sResolvingSlot, &resolving)) {
+  if (!JS_GetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, &resolving)) {
     return JS_FALSE;
   }
 
-  if (JSVAL_TO_BOOLEAN(resolving)) {
+  if (HAS_FLAGS(resolving, FLAG_RESOLVING)) {
     // Allow us to define a property on ourselves.
     return JS_TRUE;
   }
 
   JSObject *wrappedObj = GetWrappedObject(cx, obj);
   if (!wrappedObj) {
     return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
   }
@@ -581,17 +570,17 @@ XPC_XOW_AddProperty(JSContext *cx, JSObj
     if (rv == NS_ERROR_DOM_PROP_ACCESS_DENIED) {
       // Can't override properties on foreign objects.
       return ThrowException(rv, cx);
     }
     return JS_FALSE;
   }
 
   // Same origin, pass this request along.
-  return XPCWrapper::AddProperty(cx, obj, wrappedObj, id, vp);
+  return XPCWrapper::AddProperty(cx, obj, JS_TRUE, wrappedObj, id, vp);
 }
 
 static JSBool
 XPC_XOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   JSObject *wrappedObj = GetWrappedObject(cx, obj);
   if (!wrappedObj) {
     return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
@@ -833,33 +822,36 @@ XPC_XOW_NewResolve(JSContext *cx, JSObje
     // We're out! We're allowed to resolve this property.
     return XPCWrapper::ResolveNativeProperty(cx, obj, wrappedObj, wn, id,
                                              flags, objp, JS_FALSE);
 
   }
 
   if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
     jsval oldSlotVal;
-    if (!::JS_GetReservedSlot(cx, obj, XPCWrapper::sResolvingSlot, &oldSlotVal) ||
-        !::JS_SetReservedSlot(cx, obj, XPCWrapper::sResolvingSlot, JSVAL_TRUE)) {
+    if (!JS_GetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, &oldSlotVal) ||
+        !JS_SetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot,
+                            INT_TO_JSVAL(JSVAL_TO_INT(oldSlotVal) |
+                                         FLAG_RESOLVING))) {
       return JS_FALSE;
     }
 
     JSBool ok = JS_DefineFunction(cx, obj, "toString",
                                   XPC_XOW_toString, 0, 0) != nsnull;
 
-    if (ok && (ok = ::JS_SetReservedSlot(cx, obj, XPCWrapper::sResolvingSlot,
-                                         oldSlotVal))) {
+    JS_SetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, oldSlotVal);
+
+    if (ok) {
       *objp = obj;
     }
 
     return ok;
   }
 
-  return XPCWrapper::NewResolve(cx, obj, wrappedObj, id, flags, objp);
+  return XPCWrapper::NewResolve(cx, obj, JS_TRUE, wrappedObj, id, flags, objp);
 }
 
 static JSBool
 XPC_XOW_Convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
 {
   // Don't do any work to convert to object.
   if (type == JSTYPE_OBJECT) {
     *vp = OBJECT_TO_JSVAL(obj);
@@ -1126,18 +1118,18 @@ XPC_XOW_Iterator(JSContext *cx, JSObject
     return nsnull;
   }
 
   JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter));
 
   // Initialize our XOW.
   jsval v = OBJECT_TO_JSVAL(wrappedObj);
   if (!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sWrappedObjSlot, v) ||
-      !JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sResolvingSlot,
-                          JSVAL_FALSE) ||
+      !JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sFlagsSlot,
+                          JSVAL_ZERO) ||
       !JS_SetReservedSlot(cx, wrapperIter, XPC_XOW_ScopeSlot,
                           PRIVATE_TO_JSVAL(nsnull))) {
     return nsnull;
   }
 
   return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, wrappedObj,
                                        keysonly);
 }
--- a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp
+++ b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp
@@ -483,32 +483,32 @@ XPC_SJOW_AddProperty(JSContext *cx, JSOb
   obj = FindSafeObject(obj);
   NS_ASSERTION(obj != nsnull, "FindSafeObject() returned null in class hook!");
 
   // Do nothing here if we're in the middle of resolving a property on
   // this safe wrapper.
   jsval isResolving;
   JSBool ok = ::JS_GetReservedSlot(cx, obj, XPC_SJOW_SLOT_IS_RESOLVING,
                                    &isResolving);
-  if (!ok || JSVAL_TO_BOOLEAN(isResolving)) {
+  if (!ok || HAS_FLAGS(isResolving, FLAG_RESOLVING)) {
     return ok;
   }
 
   JSObject *unsafeObj = GetUnsafeObject(obj);
   if (!unsafeObj) {
     return ThrowException(NS_ERROR_UNEXPECTED, cx);
   }
 
   // Check that the caller can access the unsafe object.
   if (!CanCallerAccess(cx, unsafeObj)) {
     // CanCallerAccess() already threw for us.
     return JS_FALSE;
   }
 
-  return XPCWrapper::AddProperty(cx, obj, unsafeObj, id, vp);
+  return XPCWrapper::AddProperty(cx, obj, JS_FALSE, unsafeObj, id, vp);
 }
 
 static JSBool
 XPC_SJOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   JSObject *unsafeObj = GetUnsafeObject(obj);
   if (!unsafeObj) {
     return ThrowException(NS_ERROR_UNEXPECTED, cx);
@@ -673,17 +673,17 @@ XPC_SJOW_NewResolve(JSContext *cx, JSObj
 
   // Resolve toString specially.
   if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
     *objp = obj;
     return JS_DefineFunction(cx, obj, "toString",
                              XPC_SJOW_toString, 0, 0) != nsnull;
   }
 
-  return XPCWrapper::NewResolve(cx, obj, unsafeObj, id, flags, objp);
+  return XPCWrapper::NewResolve(cx, obj, JS_FALSE, unsafeObj, id, flags, objp);
 }
 
 static JSBool
 XPC_SJOW_Convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
 {
   NS_ASSERTION(type != JSTYPE_STRING, "toString failed us");
   return JS_TRUE;
 }
@@ -951,17 +951,17 @@ XPC_SJOW_Construct(JSContext *cx, JSObje
                                  objToWrap);
 
   if (!wrapperObj) {
     // JS_NewObjectWithGivenProto already threw.
     return JS_FALSE;
   }
 
   if (!::JS_SetReservedSlot(cx, wrapperObj, XPC_SJOW_SLOT_IS_RESOLVING,
-                            BOOLEAN_TO_JSVAL(JS_FALSE))) {
+                            JSVAL_ZERO)) {
     return JS_FALSE;
   }
 
   *rval = OBJECT_TO_JSVAL(wrapperObj);
 
   return JS_TRUE;
 }
 
@@ -1038,17 +1038,17 @@ XPC_SJOW_Iterator(JSContext *cx, JSObjec
   JSObject *wrapperIter =
     ::JS_NewObjectWithGivenProto(cx, &sXPC_SJOW_JSClass.base, nsnull,
                                  unsafeObj);
   if (!wrapperIter) {
     return nsnull;
   }
 
   if (!::JS_SetReservedSlot(cx, wrapperIter, XPC_SJOW_SLOT_IS_RESOLVING,
-                            BOOLEAN_TO_JSVAL(JS_FALSE))) {
+                            JSVAL_ZERO)) {
     return nsnull;
   }
 
   JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter));
 
   // Initialize the wrapper.
   return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, unsafeObj,
                                        keysonly);
new file mode 100644
--- /dev/null
+++ b/js/src/xpconnect/src/XPCSystemOnlyWrapper.cpp
@@ -0,0 +1,638 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78 sts=2: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Blake Kaplan <mrbkap@gmail.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "xpcprivate.h"
+#include "nsDOMError.h"
+#include "jsdbgapi.h"
+#include "jscntxt.h"  // For JSAutoTempValueRooter.
+#include "XPCNativeWrapper.h"
+#include "XPCWrapper.h"
+
+// This file implements a wrapper around trusted objects that allows them to
+// be safely injected into untrusted code.
+
+static JSBool
+XPC_SOW_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+
+static JSBool
+XPC_SOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+
+static JSBool
+XPC_SOW_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+
+static JSBool
+XPC_SOW_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
+
+static JSBool
+XPC_SOW_Enumerate(JSContext *cx, JSObject *obj);
+
+static JSBool
+XPC_SOW_NewResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
+                   JSObject **objp);
+
+static JSBool
+XPC_SOW_Convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp);
+
+static JSBool
+XPC_SOW_CheckAccess(JSContext *cx, JSObject *obj, jsval id, JSAccessMode mode,
+                    jsval *vp);
+
+static JSBool
+XPC_SOW_HasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
+
+static JSBool
+XPC_SOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
+
+static JSObject *
+XPC_SOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
+
+static JSObject *
+XPC_SOW_WrappedObject(JSContext *cx, JSObject *obj);
+
+JSExtendedClass sXPC_SOW_JSClass = {
+  // JSClass (JSExtendedClass.base) initialization
+  { "SystemOnlyWrapper",
+    JSCLASS_NEW_RESOLVE | JSCLASS_IS_EXTENDED |
+    JSCLASS_HAS_RESERVED_SLOTS(XPCWrapper::sNumSlots),
+    XPC_SOW_AddProperty, XPC_SOW_DelProperty,
+    XPC_SOW_GetProperty, XPC_SOW_SetProperty,
+    XPC_SOW_Enumerate,   (JSResolveOp)XPC_SOW_NewResolve,
+    XPC_SOW_Convert,     JS_FinalizeStub,
+    nsnull,              XPC_SOW_CheckAccess,
+    nsnull,              nsnull,
+    nsnull,              XPC_SOW_HasInstance,
+    nsnull,              nsnull
+  },
+
+  // JSExtendedClass initialization
+  XPC_SOW_Equality,
+  nsnull,             // outerObject
+  nsnull,             // innerObject
+  XPC_SOW_Iterator,
+  XPC_SOW_WrappedObject,
+  JSCLASS_NO_RESERVED_MEMBERS
+};
+
+static JSBool
+XPC_SOW_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+                 jsval *rval);
+
+// Throws an exception on context |cx|.
+static inline JSBool
+ThrowException(nsresult rv, JSContext *cx)
+{
+  return XPCWrapper::ThrowException(rv, cx);
+}
+
+// Like GetWrappedObject, but works on other types of wrappers, too.
+// TODO Move to XPCWrapper?
+static inline JSObject *
+GetWrappedJSObject(JSContext *cx, JSObject *obj)
+{
+  JSClass *clasp = STOBJ_GET_CLASS(obj);
+  if (!(clasp->flags & JSCLASS_IS_EXTENDED)) {
+    return obj;
+  }
+
+  JSExtendedClass *xclasp = (JSExtendedClass *)clasp;
+  if (!xclasp->wrappedObject) {
+    return obj;
+  }
+
+  return xclasp->wrappedObject(cx, obj);
+}
+
+// Get the (possibly non-existant) SOW off of an object
+static inline
+JSObject *
+GetWrapper(JSObject *obj)
+{
+  while (STOBJ_GET_CLASS(obj) != &sXPC_SOW_JSClass.base) {
+    obj = STOBJ_GET_PROTO(obj);
+    if (!obj) {
+      break;
+    }
+  }
+
+  return obj;
+}
+
+static inline
+JSObject *
+GetWrappedObject(JSContext *cx, JSObject *wrapper)
+{
+  return XPCWrapper::UnwrapGeneric(cx, &sXPC_SOW_JSClass, wrapper);
+}
+
+JSBool
+AllowedToAct(JSContext *cx, jsval idval)
+{
+  nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
+  if (!ssm) {
+    return JS_TRUE;
+  }
+
+  JSStackFrame *fp;
+  nsIPrincipal *principal = ssm->GetCxSubjectPrincipalAndFrame(cx, &fp);
+  if (!principal) {
+    return ThrowException(NS_ERROR_UNEXPECTED, cx);
+  }
+
+  void *annotation = JS_GetFrameAnnotation(cx, fp);
+  PRBool privileged;
+  if (fp &&
+      NS_SUCCEEDED(principal->IsCapabilityEnabled("UniversalXPConnect",
+                                                  annotation,
+                                                  &privileged)) &&
+      privileged) {
+    // UniversalXPConnect things are allowed to touch us.
+    return JS_TRUE;
+  }
+
+  // XXX HACK EWW! Allow chrome://global/ access to these things, even
+  // if they've been cloned into less privileged contexts.
+  static const char prefix[] = "chrome://global/";
+  const char *filename = fp->script->filename;
+  if (filename && !strncmp(filename, prefix, NS_ARRAY_LENGTH(prefix) - 1)) {
+    return JS_TRUE;
+  }
+
+  if (JSVAL_IS_VOID(idval)) {
+    ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
+  } else {
+    // TODO Localize me?
+    JSString *str = JS_ValueToString(cx, idval);
+    if (str) {
+      JS_ReportError(cx, "Permission denied to access property '%hs' from a non-chrome context",
+                     JS_GetStringChars(str));
+    }
+  }
+
+  return JS_FALSE;
+}
+
+static JSBool
+XPC_SOW_FunctionWrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+                        jsval *rval)
+{
+  if (!AllowedToAct(cx, JSVAL_VOID)) {
+    return JS_FALSE;
+  }
+
+  JSObject *wrappedObj;
+
+  // Allow 'this' to be either a SOW, in which case we unwrap it or something
+  // that isn't a SOW.  We disallow invalid SOWs that have no wrapped object.
+  // We do this so that it's possible to use this function with .call on
+  // related objects that are not system only.
+
+  wrappedObj = GetWrapper(obj);
+  if (wrappedObj) {
+    wrappedObj = GetWrappedObject(cx, wrappedObj);
+    if (!wrappedObj) {
+      return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
+    }
+  } else {
+    wrappedObj = obj;
+  }
+
+  JSObject *funObj = JSVAL_TO_OBJECT(argv[-2]);
+  jsval funToCall;
+  if (!JS_GetReservedSlot(cx, funObj, XPCWrapper::eWrappedFunctionSlot,
+                          &funToCall)) {
+    return JS_FALSE;
+  }
+
+  return JS_CallFunctionValue(cx, wrappedObj, funToCall, argc, argv, rval);
+}
+
+JSBool
+XPC_SOW_WrapFunction(JSContext *cx, JSObject *outerObj, JSObject *funobj,
+                     jsval *rval)
+{
+  jsval funobjVal = OBJECT_TO_JSVAL(funobj);
+  JSFunction *wrappedFun =
+    reinterpret_cast<JSFunction *>(xpc_GetJSPrivate(funobj));
+  JSNative native = JS_GetFunctionNative(cx, wrappedFun);
+  if (!native || native == XPC_SOW_FunctionWrapper) {
+    *rval = funobjVal;
+    return JS_TRUE;
+  }
+
+  JSFunction *funWrapper =
+    JS_NewFunction(cx, XPC_SOW_FunctionWrapper,
+                   JS_GetFunctionArity(wrappedFun), 0,
+                   JS_GetGlobalForObject(cx, outerObj),
+                   JS_GetFunctionName(wrappedFun));
+  if (!funWrapper) {
+    return JS_FALSE;
+  }
+
+  JSObject *funWrapperObj = JS_GetFunctionObject(funWrapper);
+  *rval = OBJECT_TO_JSVAL(funWrapperObj);
+
+  return JS_SetReservedSlot(cx, funWrapperObj,
+                            XPCWrapper::eWrappedFunctionSlot,
+                            funobjVal);
+}
+
+static JSBool
+XPC_SOW_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+  NS_ASSERTION(STOBJ_GET_CLASS(obj) == &sXPC_SOW_JSClass.base, "Wrong object");
+
+  jsval resolving;
+  if (!JS_GetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, &resolving)) {
+    return JS_FALSE;
+  }
+
+  if (HAS_FLAGS(resolving, FLAG_RESOLVING)) {
+    // Allow us to define a property on ourselves.
+    return JS_TRUE;
+  }
+
+  if (!AllowedToAct(cx, id)) {
+    return JS_FALSE;
+  }
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    return JS_TRUE;
+  }
+
+  return XPCWrapper::AddProperty(cx, obj, JS_TRUE, wrappedObj, id, vp);
+}
+
+static JSBool
+XPC_SOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
+  }
+
+  if (!AllowedToAct(cx, id)) {
+    return JS_FALSE;
+  }
+
+  return XPCWrapper::DelProperty(cx, wrappedObj, id, vp);
+}
+
+static JSBool
+XPC_SOW_GetOrSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp,
+                         JSBool isSet)
+{
+  if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
+    return JS_TRUE;
+  }
+
+  obj = GetWrapper(obj);
+  if (!obj) {
+    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
+  }
+
+  if (!AllowedToAct(cx, id)) {
+    return JS_FALSE;
+  }
+
+  JSAutoTempValueRooter tvr(cx, 1, vp);
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
+  }
+
+  if (isSet && id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_PROTO)) {
+    // No setting __proto__ on my object.
+    return ThrowException(NS_ERROR_INVALID_ARG, cx); // XXX better error message
+  }
+
+  jsid interned_id;
+  if (!JS_ValueToId(cx, id, &interned_id)) {
+    return JS_FALSE;
+  }
+
+  return isSet
+         ? JS_SetPropertyById(cx, wrappedObj, interned_id, vp)
+         : JS_GetPropertyById(cx, wrappedObj, interned_id, vp);
+}
+
+static JSBool
+XPC_SOW_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+  return XPC_SOW_GetOrSetProperty(cx, obj, id, vp, JS_FALSE);
+}
+
+static JSBool
+XPC_SOW_SetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
+{
+  return XPC_SOW_GetOrSetProperty(cx, obj, id, vp, JS_TRUE);
+}
+
+static JSBool
+XPC_SOW_Enumerate(JSContext *cx, JSObject *obj)
+{
+  obj = GetWrapper(obj);
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    // Nothing to enumerate.
+    return JS_TRUE;
+  }
+
+  if (!AllowedToAct(cx, JSVAL_VOID)) {
+    return JS_FALSE;
+  }
+
+  return XPCWrapper::Enumerate(cx, obj, wrappedObj);
+}
+
+static JSBool
+XPC_SOW_NewResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
+                   JSObject **objp)
+{
+  obj = GetWrapper(obj);
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    // No wrappedObj means that this is probably the prototype.
+    *objp = nsnull;
+    return JS_TRUE;
+  }
+
+  if (!AllowedToAct(cx, id)) {
+    return JS_FALSE;
+  }
+
+  if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
+    jsval oldSlotVal;
+    if (!JS_GetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, &oldSlotVal) ||
+        !JS_SetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot,
+                            INT_TO_JSVAL(JSVAL_TO_INT(oldSlotVal) |
+                                         FLAG_RESOLVING))) {
+      return JS_FALSE;
+    }
+
+    JSBool ok = JS_DefineFunction(cx, obj, "toString",
+                                  XPC_SOW_toString, 0, 0) != nsnull;
+
+    JS_SetReservedSlot(cx, obj, XPCWrapper::sFlagsSlot, oldSlotVal);
+
+    if (ok) {
+      *objp = obj;
+    }
+
+    return ok;
+  }
+
+  return XPCWrapper::NewResolve(cx, obj, JS_TRUE, wrappedObj, id, flags, objp);
+}
+
+static JSBool
+XPC_SOW_Convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
+{
+  if (!AllowedToAct(cx, JSVAL_VOID)) {
+    return JS_FALSE;
+  }
+
+  // Don't do any work to convert to object.
+  if (type == JSTYPE_OBJECT) {
+    *vp = OBJECT_TO_JSVAL(obj);
+    return JS_TRUE;
+  }
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    // Converting the prototype to something.
+
+    if (type == JSTYPE_STRING || type == JSTYPE_VOID) {
+      return XPC_SOW_toString(cx, obj, 0, nsnull, vp);
+    }
+
+    *vp = OBJECT_TO_JSVAL(obj);
+    return JS_TRUE;
+  }
+
+  return STOBJ_GET_CLASS(wrappedObj)->convert(cx, wrappedObj, type, vp);
+}
+
+static JSBool
+XPC_SOW_CheckAccess(JSContext *cx, JSObject *obj, jsval prop, JSAccessMode mode,
+                    jsval *vp)
+{
+  // Simply forward checkAccess to our wrapped object. It's already expecting
+  // untrusted things to ask it about accesses.
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    *vp = JSVAL_VOID;
+    return JS_TRUE;
+  }
+
+  uintN junk;
+  jsid id;
+  return JS_ValueToId(cx, prop, &id) &&
+         JS_CheckAccess(cx, wrappedObj, id, mode, vp, &junk);
+}
+
+static JSBool
+XPC_SOW_HasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
+{
+  if (!AllowedToAct(cx, JSVAL_VOID)) {
+    return JS_FALSE;
+  }
+
+  JSObject *iface = GetWrappedObject(cx, obj);
+  if (!iface) {
+    *bp = JS_FALSE;
+    return JS_TRUE;
+  }
+
+  JSClass *clasp = STOBJ_GET_CLASS(iface);
+
+  *bp = JS_FALSE;
+  if (!clasp->hasInstance) {
+    return JS_TRUE;
+  }
+
+  // Prematurely unwrap the left hand side. This isn't necessary, but could be
+  // faster than waiting until XPCWrappedNative::GetWrappedNativeOfJSObject to
+  // do it.
+  if (!JSVAL_IS_PRIMITIVE(v)) {
+    JSObject *test = JSVAL_TO_OBJECT(v);
+
+    // GetWrappedObject does a class check.
+    test = GetWrappedObject(cx, test);
+    if (test) {
+      v = OBJECT_TO_JSVAL(test);
+    }
+  }
+
+  return clasp->hasInstance(cx, iface, v, bp);
+}
+
+static JSBool
+XPC_SOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
+{
+  // Delegate to our wrapped object.
+  if (JSVAL_IS_PRIMITIVE(v)) {
+    *bp = JS_FALSE;
+    return JS_TRUE;
+  }
+
+  if (obj == JSVAL_TO_OBJECT(v)) {
+    *bp = JS_TRUE;
+    return JS_TRUE;
+  }
+
+  JSObject *lhs = GetWrappedObject(cx, obj);
+  JSObject *rhs = GetWrappedJSObject(cx, JSVAL_TO_OBJECT(v));
+  if (lhs == rhs) {
+    *bp = JS_TRUE;
+    return JS_TRUE;
+  }
+
+  if (lhs) {
+    // Delegate to our wrapped object if we can.
+    JSClass *clasp = STOBJ_GET_CLASS(lhs);
+    if (clasp->flags & JSCLASS_IS_EXTENDED) {
+      JSExtendedClass *xclasp = (JSExtendedClass *) clasp;
+      // NB: JSExtendedClass.equality is a required field.
+      return xclasp->equality(cx, lhs, OBJECT_TO_JSVAL(rhs), bp);
+    }
+  }
+
+  // We know rhs is non-null.
+  JSClass *clasp = STOBJ_GET_CLASS(rhs);
+  if (clasp->flags & JSCLASS_IS_EXTENDED) {
+    JSExtendedClass *xclasp = (JSExtendedClass *) clasp;
+    // NB: JSExtendedClass.equality is a required field.
+    return xclasp->equality(cx, rhs, OBJECT_TO_JSVAL(lhs), bp);
+  }
+
+  *bp = JS_FALSE;
+  return JS_TRUE;
+}
+
+static JSObject *
+XPC_SOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly)
+{
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    ThrowException(NS_ERROR_INVALID_ARG, cx);
+    return nsnull;
+  }
+
+  JSObject *wrapperIter = JS_NewObject(cx, &sXPC_SOW_JSClass.base, nsnull,
+                                       JS_GetGlobalForObject(cx, obj));
+  if (!wrapperIter) {
+    return nsnull;
+  }
+
+  JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter));
+
+  // Initialize our SOW.
+  jsval v = OBJECT_TO_JSVAL(wrappedObj);
+  if (!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sWrappedObjSlot, v) ||
+      !JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sFlagsSlot,
+                          JSVAL_ZERO)) {
+    return nsnull;
+  }
+
+  return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, wrappedObj,
+                                       keysonly);
+}
+
+static JSObject *
+XPC_SOW_WrappedObject(JSContext *cx, JSObject *obj)
+{
+  return GetWrappedObject(cx, obj);
+}
+
+static JSBool
+XPC_SOW_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
+                 jsval *rval)
+{
+  if (!AllowedToAct(cx, JSVAL_VOID)) {
+    return JS_FALSE;
+  }
+
+  obj = GetWrapper(obj);
+  if (!obj) {
+    return ThrowException(NS_ERROR_UNEXPECTED, cx);
+  }
+
+  JSObject *wrappedObj = GetWrappedObject(cx, obj);
+  if (!wrappedObj) {
+    // Someone's calling toString on our prototype.
+    NS_NAMED_LITERAL_CSTRING(protoString, "[object XPCCrossOriginWrapper]");
+    JSString *str =
+      JS_NewStringCopyN(cx, protoString.get(), protoString.Length());
+    if (!str) {
+      return JS_FALSE;
+    }
+    *rval = STRING_TO_JSVAL(str);
+    return JS_TRUE;
+  }
+
+  XPCWrappedNative *wn =
+    XPCWrappedNative::GetWrappedNativeOfJSObject(cx, wrappedObj);
+  return XPCWrapper::NativeToString(cx, wn, argc, argv, rval, JS_FALSE);
+}
+
+JSBool
+XPC_SOW_WrapObject(JSContext *cx, JSObject *parent, jsval v,
+                   jsval *vp)
+{
+  JSObject *wrapperObj =
+    JS_NewObjectWithGivenProto(cx, &sXPC_SOW_JSClass.base, NULL, parent);
+  if (!wrapperObj) {
+    return JS_FALSE;
+  }
+
+  *vp = OBJECT_TO_JSVAL(wrapperObj);
+  JSAutoTempValueRooter tvr(cx, *vp);
+
+  if (!JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sWrappedObjSlot, v) ||
+      !JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sFlagsSlot,
+                          JSVAL_ZERO)) {
+    return JS_FALSE;
+  }
+
+  return JS_TRUE;
+}
--- a/js/src/xpconnect/src/XPCWrapper.cpp
+++ b/js/src/xpconnect/src/XPCWrapper.cpp
@@ -36,34 +36,73 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "XPCWrapper.h"
+#include "XPCNativeWrapper.h"
 
 const PRUint32
 XPCWrapper::sWrappedObjSlot = 1;
 
 const PRUint32
-XPCWrapper::sResolvingSlot = 0;
+XPCWrapper::sFlagsSlot = 0;
 
 const PRUint32
 XPCWrapper::sNumSlots = 2;
 
 JSNative
 XPCWrapper::sEvalNative = nsnull;
 
 const PRUint32
 XPCWrapper::sSecMgrSetProp = nsIXPCSecurityManager::ACCESS_SET_PROPERTY;
 const PRUint32
 XPCWrapper::sSecMgrGetProp = nsIXPCSecurityManager::ACCESS_GET_PROPERTY;
 
+// static
+JSObject *
+XPCWrapper::Unwrap(JSContext *cx, JSObject *wrapper)
+{
+  JSClass *clasp = STOBJ_GET_CLASS(wrapper);
+  if (clasp == &sXPC_XOW_JSClass.base) {
+    return UnwrapXOW(cx, wrapper);
+  }
+
+  if (XPCNativeWrapper::IsNativeWrapperClass(clasp)) {
+    XPCWrappedNative *wrappedObj;
+    if (!XPCNativeWrapper::GetWrappedNative(cx, wrapper, &wrappedObj) ||
+        !wrappedObj) {
+      return nsnull;
+    }
+
+    return wrappedObj->GetFlatJSObject();
+  }
+
+  if (clasp == &sXPC_SJOW_JSClass.base) {
+    JSObject *wrappedObj = STOBJ_GET_PARENT(wrapper);
+
+    if (NS_FAILED(CanAccessWrapper(cx, wrappedObj))) {
+      JS_ClearPendingException(cx);
+
+      return nsnull;
+    }
+
+    return wrappedObj;
+  }
+
+  if (clasp == &sXPC_SOW_JSClass.base) {
+    return UnwrapSOW(cx, wrapper);
+  }
+
+  return nsnull;
+}
+
 static void
 IteratorFinalize(JSContext *cx, JSObject *obj)
 {
   jsval v;
   JS_GetReservedSlot(cx, obj, 0, &v);
 
   JSIdArray *ida = reinterpret_cast<JSIdArray *>(JSVAL_TO_PRIVATE(v));
   if (ida) {
@@ -192,28 +231,27 @@ XPCWrapper::CreateIteratorObj(JSContext 
   }
 
   return iterObj;
 }
 
 // static
 JSBool
 XPCWrapper::AddProperty(JSContext *cx, JSObject *wrapperObj,
-                        JSObject *innerObj, jsval id, jsval *vp)
+                        JSBool wantGetterSetter, JSObject *innerObj, jsval id,
+                        jsval *vp)
 {
   jsid interned_id;
   if (!::JS_ValueToId(cx, id, &interned_id)) {
     return JS_FALSE;
   }
 
-  JSBool isXOW = (STOBJ_GET_CLASS(wrapperObj) == &sXPC_XOW_JSClass.base);
-
   JSPropertyDescriptor desc;
   if (!GetPropertyAttrs(cx, wrapperObj, interned_id, JSRESOLVE_QUALIFIED,
-                        isXOW, &desc)) {
+                        wantGetterSetter, &desc)) {
     return JS_FALSE;
   }
 
   NS_ASSERTION(desc.obj == wrapperObj,
                "What weird wrapper are we using?");
 
   return JS_DefinePropertyById(cx, innerObj, interned_id, desc.value,
                                desc.getter, desc.setter, desc.attrs);
@@ -284,51 +322,50 @@ XPCWrapper::Enumerate(JSContext *cx, JSO
   JS_DestroyIdArray(cx, ida);
 
   return ok;
 }
 
 // static
 JSBool
 XPCWrapper::NewResolve(JSContext *cx, JSObject *wrapperObj,
-                       JSObject *innerObj, jsval id, uintN flags,
-                       JSObject **objp, JSBool preserveVal)
+                       JSBool wantDetails, JSObject *innerObj, jsval id,
+                       uintN flags, JSObject **objp)
 {
   jsid interned_id;
   if (!::JS_ValueToId(cx, id, &interned_id)) {
     return JS_FALSE;
   }
 
-  JSBool isXOW = (STOBJ_GET_CLASS(wrapperObj) == &sXPC_XOW_JSClass.base);
-
   JSPropertyDescriptor desc;
-  if (!GetPropertyAttrs(cx, innerObj, interned_id, flags, isXOW, &desc)) {
+  if (!GetPropertyAttrs(cx, innerObj, interned_id, flags, wantDetails, &desc)) {
     return JS_FALSE;
   }
 
   if (!desc.obj) {
     // Nothing to define.
     return JS_TRUE;
   }
 
-  if (!preserveVal) {
-    desc.value = JSVAL_VOID;
-  }
+  desc.value = JSVAL_VOID;
 
-  jsval oldSlotVal;
-  if (!::JS_GetReservedSlot(cx, wrapperObj, sResolvingSlot, &oldSlotVal) ||
-      !::JS_SetReservedSlot(cx, wrapperObj, sResolvingSlot, JSVAL_TRUE)) {
+  jsval oldFlags;
+  if (!::JS_GetReservedSlot(cx, wrapperObj, sFlagsSlot, &oldFlags) ||
+      !::JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot,
+                            INT_TO_JSVAL(JSVAL_TO_INT(oldFlags) |
+                                         FLAG_RESOLVING))) {
     return JS_FALSE;
   }
 
   JSBool ok = JS_DefinePropertyById(cx, wrapperObj, interned_id, desc.value,
                                     desc.getter, desc.setter, desc.attrs);
 
-  if (ok && (ok = ::JS_SetReservedSlot(cx, wrapperObj, sResolvingSlot,
-                                       oldSlotVal))) {
+  JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot, oldFlags);
+
+  if (ok) {
     *objp = wrapperObj;
   }
 
   return ok;
 }
 
 // static
 JSBool
@@ -343,28 +380,21 @@ XPCWrapper::ResolveNativeProperty(JSCont
   // For "constructor" we don't want to call into the resolve hooks on the
   // wrapped native, since that would give the wrong constructor.
   if (NATIVE_HAS_FLAG(wn, WantNewResolve) &&
       id != GetRTStringByIndex(cx, XPCJSRuntime::IDX_CONSTRUCTOR)) {
 
     // Mark ourselves as resolving so our AddProperty hook can do the
     // right thing here.
     jsval oldFlags;
-    if (isNativeWrapper) {
-      if (!::JS_GetReservedSlot(cx, wrapperObj, 0, &oldFlags) ||
-          !::JS_SetReservedSlot(cx, wrapperObj, 0,
-                                INT_TO_JSVAL(JSVAL_TO_INT(oldFlags) |
-                                             FLAG_RESOLVING))) {
-        return JS_FALSE;
-      }
-    } else {
-      if (!::JS_GetReservedSlot(cx, wrapperObj, sResolvingSlot, &oldFlags) ||
-          !::JS_SetReservedSlot(cx, wrapperObj, sResolvingSlot, JSVAL_TRUE)) {
-        return JS_FALSE;
-      }
+    if (!::JS_GetReservedSlot(cx, wrapperObj, sFlagsSlot, &oldFlags) ||
+        !::JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot,
+                              INT_TO_JSVAL(JSVAL_TO_INT(oldFlags) |
+                                           FLAG_RESOLVING))) {
+      return JS_FALSE;
     }
 
     XPCWrappedNative* oldResolvingWrapper = nsnull;
     JSBool allowPropMods =
       NATIVE_HAS_FLAG(wn, AllowPropModsDuringResolve);
     if (allowPropMods) {
       oldResolvingWrapper = ccx.SetResolvingWrapper(wn);
     }
@@ -374,19 +404,17 @@ XPCWrapper::ResolveNativeProperty(JSCont
     nsresult rv = wn->GetScriptableInfo()->
       GetCallback()->NewResolve(wn, cx, wrapperObj, id, flags,
                                 &newObj, &retval);
 
     if (allowPropMods) {
       ccx.SetResolvingWrapper(oldResolvingWrapper);
     }
 
-    if (!::JS_SetReservedSlot(cx, wrapperObj,
-                              isNativeWrapper ? 0 : sResolvingSlot,
-                              oldFlags)) {
+    if (!::JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot, oldFlags)) {
       return JS_FALSE;
     }
 
     if (NS_FAILED(rv)) {
       return ThrowException(rv, cx);
     }
 
     if (newObj) {
@@ -400,17 +428,23 @@ XPCWrapper::ResolveNativeProperty(JSCont
         }
 #endif
         // Note that we don't need to preserve the wrapper here, since this is
         // not an "expando" property if the scriptable newResolve hook found it.
         *objp = newObj;
         return retval;
       }
 
-      return NewResolve(cx, wrapperObj, innerObj, id, flags, objp, JS_TRUE);
+      // The scriptable helper resolved this property to a *different* object.
+      // We don't know what to do for now (this can't currently happen in
+      // Mozilla) so throw.
+      // I suspect that we'd need to redo the security check on the new object
+      // (if it has a different class than the original object) and then call
+      // ResolveNativeProperty with *that* as the inner object.
+      return ThrowException(NS_ERROR_NOT_IMPLEMENTED, cx);
     }
   }
 
   if (!JSVAL_IS_STRING(id)) {
     // A non-string id is being resolved. Won't be found here, return
     // early.
 
     return MaybePreserveWrapper(cx, wn, flags);
@@ -502,29 +536,31 @@ XPCWrapper::ResolveNativeProperty(JSCont
   }
 
   // Make sure v doesn't go away while we mess with it.
   AUTO_MARK_JSVAL(ccx, v);
 
   // XPCNativeWrapper doesn't need to do this.
   jsval oldFlags;
   if (!isNativeWrapper &&
-      (!::JS_GetReservedSlot(cx, wrapperObj, sResolvingSlot, &oldFlags) ||
-       !::JS_SetReservedSlot(cx, wrapperObj, sResolvingSlot, JSVAL_TRUE))) {
+      (!::JS_GetReservedSlot(cx, wrapperObj, sFlagsSlot, &oldFlags) ||
+       !::JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot,
+                             INT_TO_JSVAL(JSVAL_TO_INT(oldFlags) |
+                                          FLAG_RESOLVING)))) {
     return JS_FALSE;
   }
 
   if (!::JS_DefineUCProperty(cx, wrapperObj, ::JS_GetStringChars(str),
                             ::JS_GetStringLength(str), v, getter, setter,
                             attrs)) {
     return JS_FALSE;
   }
 
   if (!isNativeWrapper &&
-      !::JS_SetReservedSlot(cx, wrapperObj, sResolvingSlot, oldFlags)) {
+      !::JS_SetReservedSlot(cx, wrapperObj, sFlagsSlot, oldFlags)) {
     return JS_FALSE;
   }
 
   *objp = wrapperObj;
 
   return JS_TRUE;
 }
 
--- a/js/src/xpconnect/src/XPCWrapper.h
+++ b/js/src/xpconnect/src/XPCWrapper.h
@@ -76,16 +76,20 @@ XPC_XOW_RewrapIfNeeded(JSContext *cx, JS
 
 JSBool
 XPC_XOW_WrapperMoved(JSContext *cx, XPCWrappedNative *innerObj,
                      XPCWrappedNativeScope *newScope);
 
 nsresult
 CanAccessWrapper(JSContext *cx, JSObject *wrappedObj);
 
+// Used by UnwrapSOW below.
+JSBool
+AllowedToAct(JSContext *cx, jsval idval);
+
 JSBool
 XPCNativeWrapperCtor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                      jsval *rval);
 
 inline JSBool
 XPC_XOW_ClassNeedsXOW(const char *name)
 {
   switch (*name) {
@@ -102,53 +106,54 @@ XPC_XOW_ClassNeedsXOW(const char *name)
       return strcmp(name, "FrameElement") == 0;
     default:
       break;
   }
 
   return JS_FALSE;
 }
 
+extern JSExtendedClass sXPC_SJOW_JSClass;
+extern JSExtendedClass sXPC_SOW_JSClass;
 extern JSExtendedClass sXPC_XOW_JSClass;
-extern JSExtendedClass sXPC_SJOW_JSClass;
 
 // This class wraps some common functionality between the three existing
 // wrappers. Its main purpose is to allow XPCCrossOriginWrapper to act both
 // as an XPCSafeSJSObjectWrapper and as an XPCNativeWrapper when required to
 // do so (the decision is based on the principals of the wrapper and wrapped
 // objects).
 class XPCWrapper
 {
 public:
   /**
    * Used by the cross origin and safe wrappers: the slot that the wrapped
    * object is held in.
    */
   static const PRUint32 sWrappedObjSlot;
 
   /**
-   * Used by the cross origin and safe wrappers: the slot that tells the
-   * AddProperty code that we're resolving a property, and therefore to not do
-   * a security check.
+   * Used by all wrappers to store flags about their state. For example,
+   * it is used when resolving a property to tell to the addProperty hook
+   * that it shouldn't perform any security checks.
    */
-  static const PRUint32 sResolvingSlot;
+  static const PRUint32 sFlagsSlot;
 
   /**
    * The base number of slots needed by code using the above constants.
    */
   static const PRUint32 sNumSlots;
 
   /**
    * Cross origin wrappers and safe JSObject wrappers both need to know
    * which native is 'eval' for various purposes.
    */
   static JSNative sEvalNative;
 
   typedef enum FunctionObjectSlot {
-    eXOWWrappedFunctionSlot = 0,
+    eWrappedFunctionSlot = 0,
     eAllAccessSlot = 1
   };
 
   // Helpful for keeping lines short:
   static const PRUint32 sSecMgrSetProp, sSecMgrGetProp;
 
   /**
    * Given a context and a global object, fill our eval native.
@@ -209,41 +214,80 @@ public:
       if (scriptNotify) {
         return NS_SUCCEEDED(scriptNotify->PreserveWrapper(wn));
       }
     }
     return JS_TRUE;
   }
 
   /**
-   * Unwraps a XPCCrossOriginWrapper into its wrapped native.
+   * Given an arbitrary object, Unwrap will return the wrapped object if the
+   * passed-in object is a wrapper that Unwrap knows about *and* the
+   * currently running code has permission to access both the wrapper and
+   * wrapped object.
+   *
+   * Since this is meant to be called from functions like
+   * XPCWrappedNative::GetWrappedNativeOfJSObject, it does not set an
+   * exception on |cx|.
    */
-  static JSObject *Unwrap(JSContext *cx, JSObject *wrapper) {
-    if (STOBJ_GET_CLASS(wrapper) != &sXPC_XOW_JSClass.base) {
+  static JSObject *Unwrap(JSContext *cx, JSObject *wrapper);
+
+  /**
+   * Unwraps objects whose class is |xclasp|.
+   */
+  static JSObject *UnwrapGeneric(JSContext *cx, const JSExtendedClass *xclasp,
+                                 JSObject *wrapper)
+  {
+    if (STOBJ_GET_CLASS(wrapper) != &xclasp->base) {
       return nsnull;
     }
 
     jsval v;
     if (!JS_GetReservedSlot(cx, wrapper, XPCWrapper::sWrappedObjSlot, &v)) {
       JS_ClearPendingException(cx);
       return nsnull;
     }
 
     if (JSVAL_IS_PRIMITIVE(v)) {
       return nsnull;
     }
 
-    JSObject *wrappedObj = JSVAL_TO_OBJECT(v);
-    nsresult rv = CanAccessWrapper(cx, wrappedObj);
-    if (NS_FAILED(rv)) {
-      JS_ClearPendingException(cx);
+    return JSVAL_TO_OBJECT(v);
+  }
+
+  static JSObject *UnwrapSOW(JSContext *cx, JSObject *wrapper) {
+    wrapper = UnwrapGeneric(cx, &sXPC_SOW_JSClass, wrapper);
+    if (!wrapper) {
       return nsnull;
     }
 
-    return wrappedObj;
+    if (!AllowedToAct(cx, JSVAL_VOID)) {
+      JS_ClearPendingException(cx);
+      wrapper = nsnull;
+    }
+
+    return wrapper;
+  }
+
+  /**
+   * Unwraps a XOW into its wrapped native.
+   */
+  static JSObject *UnwrapXOW(JSContext *cx, JSObject *wrapper) {
+    wrapper = UnwrapGeneric(cx, &sXPC_XOW_JSClass, wrapper);
+    if (!wrapper) {
+      return nsnull;
+    }
+
+    nsresult rv = CanAccessWrapper(cx, wrapper);
+    if (NS_FAILED(rv)) {
+      JS_ClearPendingException(cx);
+      wrapper = nsnull;
+    }
+
+    return wrapper;
   }
 
   /**
    * Rewraps a property if it needs to be rewrapped. Used by
    * GetOrSetNativeProperty to rewrap the return value.
    */
   static JSBool RewrapIfDeepWrapper(JSContext *cx, JSObject *obj, jsval v,
                                     jsval *rval, JSBool isNativeWrapper) {
@@ -278,17 +322,18 @@ public:
                                      JSObject *wrapperObj,
                                      JSObject *innerObj,
                                      JSBool keysonly);
 
   /**
    * Called for the common part of adding a property to obj.
    */
   static JSBool AddProperty(JSContext *cx, JSObject *wrapperObj,
-                            JSObject *innerObj, jsval id, jsval *vp);
+                            JSBool wantGetterSetter, JSObject *innerObj,
+                            jsval id, jsval *vp);
 
   /**
    * Called for the common part of deleting a property from obj.
    */
   static JSBool DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
 
   /**
    * Called to enumerate the properties of |innerObj| onto |wrapperObj|.
@@ -297,18 +342,18 @@ public:
                           JSObject *innerObj);
 
   /**
    * Resolves a property (that may be) defined on |innerObj| onto
    * |wrapperObj|. This will also resolve random, page-defined objects
    * and is therefore unsuitable for cross-origin resolution.
    */
   static JSBool NewResolve(JSContext *cx, JSObject *wrapperObj,
-                           JSObject *innerObj, jsval id, uintN flags,
-                           JSObject **objp, JSBool preserveVal = JS_FALSE);
+                           JSBool preserveVal, JSObject *innerObj,
+                           jsval id, uintN flags, JSObject **objp);
 
   /**
    * Resolve a native property named id from innerObj onto wrapperObj. The
    * native wrapper will be preserved if necessary. Note that if we resolve
    * an attribute here, we don't deal with the value until later.
    */
   static JSBool ResolveNativeProperty(JSContext *cx, JSObject *wrapperObj,
                                       JSObject *innerObj, XPCWrappedNative *wn,
--- a/js/src/xpconnect/src/nsXPConnect.cpp
+++ b/js/src/xpconnect/src/nsXPConnect.cpp
@@ -2401,16 +2401,19 @@ nsXPConnect::GetWrapperForObject(JSConte
         if(XPC_XOW_WrapObject(aJSContext, aScope, &val, wrapper))
             wrappedObj = JSVAL_TO_OBJECT(val);
     }
 
     if(!wrappedObj)
         return NS_ERROR_FAILURE;
 
     *_retval = OBJECT_TO_JSVAL(wrappedObj);
+    if(wrapper->NeedsChromeWrapper() &&
+       !XPC_SOW_WrapObject(aJSContext, aScope, *_retval, _retval))
+        return NS_ERROR_FAILURE;
     return NS_OK;
 }
 
 /* attribute JSRuntime runtime; */
 NS_IMETHODIMP
 nsXPConnect::GetRuntime(JSRuntime **runtime)
 {
     if(!runtime)
--- a/js/src/xpconnect/src/xpcconvert.cpp
+++ b/js/src/xpconnect/src/xpcconvert.cpp
@@ -1276,37 +1276,64 @@ XPCConvert::NativeInterface2JSObject(XPC
                         // Reaching across scopes from content code. Wrap
                         // the new object in a XOW.
                         if (XPC_XOW_WrapObject(ccx, scope, &v))
                             destObj = JSVAL_TO_OBJECT(v);
                         triedWrapping = JS_TRUE;
                     }
 
                     if(triedWrapping)
-                        return destObj &&
-                               CreateHolderIfNeeded(ccx, destObj, d, dest);
+                    {
+                        if(!destObj)
+                            return JS_FALSE;
+
+                        jsval wrappedObjVal = OBJECT_TO_JSVAL(destObj);
+                        AUTO_MARK_JSVAL(ccx, &wrappedObjVal);
+                        if(wrapper->NeedsChromeWrapper())
+                        {
+                            if(!XPC_SOW_WrapObject(ccx, xpcscope->GetGlobalJSObject(),
+                                                   OBJECT_TO_JSVAL(destObj),
+                                                   &wrappedObjVal))
+                                return JS_FALSE;
+                        }
+
+                        return CreateHolderIfNeeded(ccx, JSVAL_TO_OBJECT(wrappedObjVal),
+                                                    d, dest);
+                    }
                 }
             }
 
             const char *name = STOBJ_GET_CLASS(flat)->name;
             if(allowNativeWrapper &&
                !(flags & JSFILENAME_SYSTEM) &&
                !JS_IsSystemObject(ccx, flat) &&
                XPC_XOW_ClassNeedsXOW(name))
             {
                 // From here on we might create new JSObjects, so we need to
                 // make sure that wrapper stays alive.
                 if(!strongWrapper)
                     strongWrapper = wrapper;
 
+                AUTO_MARK_JSVAL(ccx, &v);
                 return XPC_XOW_WrapObject(ccx, scope, &v) &&
+                       (!wrapper->NeedsChromeWrapper() ||
+                        XPC_SOW_WrapObject(ccx, xpcscope->GetGlobalJSObject(),
+                                           v, &v)) &&
                        CreateHolderIfNeeded(ccx, JSVAL_TO_OBJECT(v), d, dest);
             }
 
-            *d = v;
+            if(allowNativeWrapper && wrapper->NeedsChromeWrapper())
+            {
+                if(!XPC_SOW_WrapObject(ccx, xpcscope->GetGlobalJSObject(), v, d))
+                    return JS_FALSE;
+            }
+            else
+            {
+                *d = v;
+            }
             if(dest)
                 *dest = strongWrapper.forget().get();
             return JS_TRUE;
         }
     }
     return JS_FALSE;
 }
 
--- a/js/src/xpconnect/src/xpcprivate.h
+++ b/js/src/xpconnect/src/xpcprivate.h
@@ -2341,18 +2341,19 @@ public:
     }
 
     // Yes, we *do* need to mark the mScriptableInfo in both cases.
     inline void TraceJS(JSTracer* trc)
     {
         if(mScriptableInfo && JS_IsGCMarkingTracer(trc))
             mScriptableInfo->Mark();
         if(HasProto()) GetProto()->TraceJS(trc);
-        if(mWrapper)
-            JS_CALL_OBJECT_TRACER(trc, mWrapper, "XPCWrappedNative::mWrapper");
+        JSObject* wrapper = GetWrapper();
+        if(wrapper)
+            JS_CALL_OBJECT_TRACER(trc, wrapper, "XPCWrappedNative::mWrapper");
         TraceOtherWrapper(trc);
     }
 
     inline void AutoTrace(JSTracer* trc)
     {
         // If this got called, we're being kept alive by someone who really
         // needs us alive and whole.  Do not let our mFlatJSObject go away.
         // This is the only time we should be tracing our mFlatJSObject,
@@ -2382,18 +2383,27 @@ public:
                    XPCWrappedNativeTearOff* to = nsnull) const;
 
     static nsresult GatherProtoScriptableCreateInfo(
                         nsIClassInfo* classInfo,
                         XPCNativeScriptableCreateInfo* sciProto);
 
     JSBool HasExternalReference() const {return mRefCnt > 1;}
 
-    JSObject* GetWrapper()              { return mWrapper; }
-    void      SetWrapper(JSObject *obj) { mWrapper = obj; }
+    JSBool NeedsChromeWrapper() { return !!(mWrapper & 1); }
+    void SetNeedsChromeWrapper() { mWrapper |= 1; }
+    JSObject* GetWrapper()
+    {
+        return (JSObject *)(mWrapper & ~1);
+    }
+    void SetWrapper(JSObject *obj)
+    {
+        JSBool reset = NeedsChromeWrapper();
+        mWrapper = PRWord(obj) | reset;
+    }
 
     void NoteTearoffs(nsCycleCollectionTraversalCallback& cb);
 
     QITableEntry* GetOffsets()
     {
         if(!HasProto() || !GetProto()->ClassIsDOMObject())
             return nsnull;
 
@@ -2449,17 +2459,17 @@ private:
     {
         XPCWrappedNativeScope*   mMaybeScope;
         XPCWrappedNativeProto*   mMaybeProto;
     };
     XPCNativeSet*                mSet;
     JSObject*                    mFlatJSObject;
     XPCNativeScriptableInfo*     mScriptableInfo;
     XPCWrappedNativeTearOffChunk mFirstChunk;
-    JSObject*                    mWrapper;
+    PRWord                       mWrapper;
 
 #ifdef XPC_CHECK_WRAPPER_THREADSAFETY
 public:
     nsCOMPtr<nsIThread>          mThread; // Don't want to overload _mOwningThread
 #endif
 };
 
 /***************************************************************************
@@ -4077,16 +4087,20 @@ XPC_SJOW_Construct(JSContext *cx, JSObje
 PRBool
 XPC_SJOW_AttachNewConstructorObject(XPCCallContext &ccx,
                                     JSObject *aGlobalObject);
 
 JSBool
 XPC_XOW_WrapObject(JSContext *cx, JSObject *parent, jsval *vp,
                    XPCWrappedNative *wn = nsnull);
 
+JSBool
+XPC_SOW_WrapObject(JSContext *cx, JSObject *parent, jsval v,
+                   jsval *vp);
+
 #ifdef XPC_IDISPATCH_SUPPORT
 // IDispatch specific classes
 #include "XPCDispPrivate.h"
 #endif
 
 /***************************************************************************/
 // Inlines use the above - include last.
 
--- a/js/src/xpconnect/src/xpcquickstubs.cpp
+++ b/js/src/xpconnect/src/xpcquickstubs.cpp
@@ -824,44 +824,29 @@ xpc_qsUnwrapThisImpl(JSContext *cx,
             wrapper = (XPCWrappedNative*) xpc_GetJSPrivate(cur);
             NS_ASSERTION(wrapper, "XPCWN wrapping nothing");
         }
         else if(clazz == &XPC_WN_Tearoff_JSClass)
         {
             wrapper = (XPCWrappedNative*) xpc_GetJSPrivate(STOBJ_GET_PARENT(cur));
             NS_ASSERTION(wrapper, "XPCWN wrapping nothing");
         }
-        else if(clazz == &sXPC_XOW_JSClass.base)
+        else
         {
             JSObject *unsafeObj = XPCWrapper::Unwrap(cx, cur);
             if(unsafeObj)
             {
                 cur = unsafeObj;
                 continue;
             }
 
             // This goto is a bug, dutifully copied from
             // XPCWrappedNative::GetWrappedNativeOfJSObject.
             goto next;
         }
-        else if(XPCNativeWrapper::IsNativeWrapperClass(clazz))
-        {
-            if(!XPCNativeWrapper::GetWrappedNative(cx, cur, &wrapper) ||
-               !wrapper)
-                goto next;
-        }
-        else if(IsXPCSafeJSObjectWrapperClass(clazz))
-        {
-            cur = STOBJ_GET_PARENT(cur);
-            NS_ASSERTION(cur, "SJOW wrapping nothing");
-            continue;
-        }
-        else {
-            goto next;
-        }
 
         rv = getNativeFromWrapper(wrapper, iid, ppThis, pThisRef, vp);
         if(NS_SUCCEEDED(rv))
             return JS_TRUE;
         if(rv != NS_ERROR_NO_INTERFACE)
             return xpc_qsThrow(cx, rv);
 
     next:
--- a/js/src/xpconnect/src/xpcwrappedjsclass.cpp
+++ b/js/src/xpconnect/src/xpcwrappedjsclass.cpp
@@ -39,16 +39,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* Sharable code and data for wrapper around JSObjects. */
 
 #include "xpcprivate.h"
 #include "nsArrayEnumerator.h"
 #include "nsWrapperCache.h"
+#include "XPCWrapper.h"
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsXPCWrappedJSClass, nsIXPCWrappedJSClass)
 
 // the value of this variable is never used - we use its address as a sentinel
 static uint32 zero_methods_descriptor;
 
 void AutoScriptEvaluate::StartEvaluating(JSErrorReporter errorReporter)
 {
@@ -731,17 +732,22 @@ nsXPCWrappedJSClass::DelegatedQueryInter
     return NS_NOINTERFACE;
 }
 
 JSObject*
 nsXPCWrappedJSClass::GetRootJSObject(XPCCallContext& ccx, JSObject* aJSObj)
 {
     JSObject* result = CallQueryInterfaceOnJSObject(ccx, aJSObj,
                                                     NS_GET_IID(nsISupports));
-    return result ? result : aJSObj;
+    if(!result)
+        return aJSObj;
+    JSObject* inner = XPCWrapper::Unwrap(ccx, result);
+    if (inner)
+        return inner;
+    return result;
 }
 
 void
 xpcWrappedJSErrorReporter(JSContext *cx, const char *message,
                           JSErrorReport *report)
 {
     if(report)
     {
--- a/js/src/xpconnect/src/xpcwrappednative.cpp
+++ b/js/src/xpconnect/src/xpcwrappednative.cpp
@@ -414,25 +414,29 @@ XPCWrappedNative::GetNewOrUsed(XPCCallCo
                                             &sciProto, &sciWrapper)))
         return NS_ERROR_FAILURE;
 
     JSObject* parent = Scope->GetGlobalJSObject();
 
     jsval newParentVal = JSVAL_NULL;
     XPCMarkableJSVal newParentVal_markable(&newParentVal);
     AutoMarkingJSVal newParentVal_automarker(ccx, &newParentVal_markable);
+    JSBool chromeOnly = JS_FALSE;
 
     if(sciWrapper.GetFlags().WantPreCreate())
     {
         JSObject* plannedParent = parent;
         nsresult rv = sciWrapper.GetCallback()->PreCreate(identity, ccx,
                                                           parent, &parent);
         if(NS_FAILED(rv))
             return rv;
 
+        chromeOnly = (rv == NS_SUCCESS_CHROME_ACCESS_ONLY);
+        rv = NS_OK;
+
         NS_ASSERTION(!XPCNativeWrapper::IsNativeWrapper(parent),
                      "Parent should never be an XPCNativeWrapper here");
 
         if(parent != plannedParent)
         {
             XPCWrappedNativeScope* betterScope =
                 XPCWrappedNativeScope::FindInJSObjectScope(ccx, parent);
             if(betterScope != Scope)
@@ -514,31 +518,35 @@ XPCWrappedNative::GetNewOrUsed(XPCCallCo
     // forget about it.
     // Note that identity is null from here on!
     identity.forget();
 
     NS_ADDREF(wrapper);
 
     NS_ASSERTION(!XPCNativeWrapper::IsNativeWrapper(parent),
                  "XPCNativeWrapper being used to parent XPCWrappedNative?");
-    
+
     if(!wrapper->Init(ccx, parent, isGlobal, &sciWrapper))
     {
         NS_RELEASE(wrapper);
         return NS_ERROR_FAILURE;
     }
 
     if(Interface && !wrapper->FindTearOff(ccx, Interface, JS_FALSE, &rv))
     {
         // Second reference will be released by the FlatJSObject's finializer.
         wrapper->Release();
         NS_ASSERTION(NS_FAILED(rv), "returning NS_OK on failure");
         return rv;
     }
 
+    if(chromeOnly)
+        wrapper->SetNeedsChromeWrapper();
+
+
 #if DEBUG_xpc_leaks
     {
         char* s = wrapper->ToString(ccx);
         NS_ASSERTION(wrapper->GetFlatJSObject(), "eh?");
         printf("Created wrapped native %s, flat JSObject is %p\n",
                s, (void*)wrapper->GetFlatJSObject());
         if(s)
             JS_smprintf_free(s);
@@ -1486,33 +1494,19 @@ return_tearoff:
                 (XPCWrappedNativeTearOff*) xpc_GetJSPrivate(cur);
             if(!to)
                 return nsnull;
             if(pTearOff)
                 *pTearOff = to;
             return wrapper;
         }
 
-        // Unwrap any XPCCrossOriginWrappers and SafeJSObjectWrappers.
+        // Unwrap any wrapper wrappers.
         JSObject *unsafeObj;
-        if(clazz == &sXPC_XOW_JSClass.base &&
-           (unsafeObj = XPCWrapper::Unwrap(cx, cur)))
-            return GetWrappedNativeOfJSObject(cx, unsafeObj, funobj, pobj2,
-                                              pTearOff);
-
-        if(XPCNativeWrapper::IsNativeWrapperClass(clazz))
-        {
-            XPCWrappedNative* wrapper;
-            if(XPCNativeWrapper::GetWrappedNative(cx, cur, &wrapper) && wrapper)
-                return GetWrappedNativeOfJSObject(cx, wrapper->GetFlatJSObject(),
-                                                  funobj, pobj2, pTearOff);
-        }
-
-        if(IsXPCSafeJSObjectWrapperClass(clazz) &&
-           (unsafeObj = STOBJ_GET_PARENT(cur)))
+        if((unsafeObj = XPCWrapper::Unwrap(cx, cur)))
             return GetWrappedNativeOfJSObject(cx, unsafeObj, funobj, pobj2,
                                               pTearOff);
     }
 
     // If we didn't find a wrapper using the given funobj and obj, try
     // again with obj's outer object, if it's got one.
 
     JSClass *clazz = STOBJ_GET_CLASS(obj);
@@ -1521,17 +1515,17 @@ return_tearoff:
         ((JSExtendedClass*)clazz)->outerObject)
     {
         JSObject *outer = ((JSExtendedClass*)clazz)->outerObject(cx, obj);
 
         // Protect against infinite recursion through XOWs.
         JSObject *unsafeObj;
         clazz = STOBJ_GET_CLASS(outer);
         if(clazz == &sXPC_XOW_JSClass.base &&
-           (unsafeObj = XPCWrapper::Unwrap(cx, outer)))
+           (unsafeObj = XPCWrapper::UnwrapXOW(cx, outer)))
         {
             outer = unsafeObj;
         }
 
         if(outer && outer != obj)
             return GetWrappedNativeOfJSObject(cx, outer, funobj, pobj2,
                                               pTearOff);
     }
--- a/testing/mochitest/MochiKit/Base.js
+++ b/testing/mochitest/MochiKit/Base.js
@@ -737,21 +737,24 @@ MochiKit.Base.update(MochiKit.Base, {
         try {
             if (typeof(o.__repr__) == 'function') {
                 return o.__repr__();
             } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
                 return o.repr();
             }
             return MochiKit.Base.reprRegistry.match(o);
         } catch (e) {
-            if (typeof(o.NAME) == 'string' && (
-                    o.toString == Function.prototype.toString ||
-                    o.toString == Object.prototype.toString
-                )) {
-                return o.NAME;
+            try {
+                if (typeof(o.NAME) == 'string' && (
+                        o.toString == Function.prototype.toString ||
+                        o.toString == Object.prototype.toString
+                    )) {
+                    return o.NAME;
+                }
+            } catch (e) {
             }
         }
         try {
             var ostring = (o + "");
         } catch (e) {
             return "[" + typeof(o) + "]";
         }
         if (typeof(o) == "function") {