Bug 542428 - Make wrappers aware of each other. r=jst
authorBlake Kaplan <mrbkap@gmail.com>
Thu, 11 Feb 2010 17:04:41 -0800
changeset 39406 8dc04a747e92d63e42815ef12717603d30f804a2
parent 39405 e99b9fbe916e73e198f989136b5315a056dde41c
child 39407 333589ef694be3a8048ac985b07a19d055b0cd6f
push id12176
push usermrbkap@mozilla.com
push dateSat, 13 Mar 2010 20:50:12 +0000
treeherdermozilla-central@333589ef694b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs542428
milestone1.9.3a3pre
Bug 542428 - Make wrappers aware of each other. r=jst
js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
js/src/xpconnect/src/XPCNativeWrapper.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/xpcwrappednativescope.cpp
js/src/xpconnect/tests/chrome/Makefile.in
js/src/xpconnect/tests/chrome/bug503926.xul
js/src/xpconnect/tests/chrome/test_bug503926.xul
js/src/xpconnect/tests/chrome/test_bug533596.xul
js/src/xpconnect/tests/chrome/test_wrappers.xul
js/src/xpconnect/tests/mochitest/Makefile.in
js/src/xpconnect/tests/mochitest/chrome_wrappers_helper.html
js/src/xpconnect/tests/mochitest/test_cows.html
--- a/js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
+++ b/js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
@@ -239,16 +239,19 @@ static JSBool
 XPC_COW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
 
 static JSObject *
 XPC_COW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
 
 static JSObject *
 XPC_COW_WrappedObject(JSContext *cx, JSObject *obj);
 
+static JSBool
+WrapFunction(JSContext *cx, JSObject *scope, JSObject *funobj, jsval *vp);
+
 using namespace XPCWrapper;
 
 namespace ChromeObjectWrapper {
 
 JSExtendedClass COWClass = {
   // JSClass (JSExtendedClass.base) initialization
   { "ChromeObjectWrapper",
     JSCLASS_NEW_RESOLVE | JSCLASS_IS_EXTENDED |
@@ -270,16 +273,20 @@ JSExtendedClass COWClass = {
   XPC_COW_Iterator,
   XPC_COW_WrappedObject,
   JSCLASS_NO_RESERVED_MEMBERS
 };
 
 JSBool
 WrapObject(JSContext *cx, JSObject *parent, jsval v, jsval *vp)
 {
+  if (JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(v))) {
+    return WrapFunction(cx, parent, JSVAL_TO_OBJECT(v), vp);
+  }
+
   JSObject *wrapperObj =
     JS_NewObjectWithGivenProto(cx, &COWClass.base, NULL, parent);
   if (!wrapperObj) {
     return JS_FALSE;
   }
 
   *vp = OBJECT_TO_JSVAL(wrapperObj);
 
@@ -356,148 +363,111 @@ static inline
 JSObject *
 GetWrappedObject(JSContext *cx, JSObject *wrapper)
 {
   return XPCWrapper::UnwrapGeneric(cx, &COWClass, wrapper);
 }
 
 // Forward declaration for the function wrapper.
 JSBool
-XPC_COW_RewrapForChrome(JSContext *cx, JSObject *wrapperObj, jsval *vp);
+RewrapForChrome(JSContext *cx, JSObject *wrapperObj, jsval *vp);
 JSBool
-XPC_COW_RewrapForContent(JSContext *cx, JSObject *wrapperObj, jsval *vp);
+RewrapForContent(JSContext *cx, JSObject *wrapperObj, jsval *vp);
 
 // This function wrapper calls a function from untrusted content into chrome.
 
 static JSBool
 XPC_COW_FunctionWrapper(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                         jsval *rval)
 {
-  JSObject *wrappedObj;
-
-  // Allow 'this' to be either a COW, in which case we unwrap it or something
-  // that isn't a COW.  We disallow invalid COWs that have no wrapped object.
-
-  wrappedObj = GetWrapper(obj);
-  if (wrappedObj) {
-    wrappedObj = GetWrappedObject(cx, wrappedObj);
-    if (!wrappedObj) {
-      return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
-    }
-  } else {
-    wrappedObj = obj;
-  }
-
   jsval funToCall;
   if (!JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(argv[-2]),
                           XPCWrapper::eWrappedFunctionSlot, &funToCall)) {
     return JS_FALSE;
   }
 
+  JSObject *scope = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(funToCall));
   for (uintN i = 0; i < argc; ++i) {
-    if (!XPC_COW_RewrapForChrome(cx, obj, &argv[i])) {
+    if (!JSVAL_IS_PRIMITIVE(argv[i]) &&
+        !RewrapObject(cx, scope, JSVAL_TO_OBJECT(argv[i]), UNKNOWN, &argv[i])) {
       return JS_FALSE;
     }
   }
 
-  if (!JS_CallFunctionValue(cx, wrappedObj, funToCall, argc, argv, rval)) {
+  if (!RewrapObject(cx, scope, obj, UNKNOWN, rval) ||
+      !JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(*rval), funToCall, argc, argv,
+                            rval)) {
     return JS_FALSE;
   }
 
-  return XPC_COW_RewrapForContent(cx, obj, rval);
+  return RewrapForContent(cx, obj, rval);
 }
 
-JSBool
-XPC_COW_WrapFunction(JSContext *cx, JSObject *outerObj, JSObject *funobj,
-                     jsval *rval)
+static JSBool
+WrapFunction(JSContext *cx, JSObject *scope, JSObject *funobj, jsval *rval)
 {
+  scope = JS_GetGlobalForObject(cx, scope);
   jsval funobjVal = OBJECT_TO_JSVAL(funobj);
   JSFunction *wrappedFun =
     reinterpret_cast<JSFunction *>(xpc_GetJSPrivate(funobj));
   JSNative native = JS_GetFunctionNative(cx, wrappedFun);
-  if (!native || native == XPC_COW_FunctionWrapper) {
-    *rval = funobjVal;
-    return JS_TRUE;
+  if (native == XPC_COW_FunctionWrapper) {
+    if (STOBJ_GET_PARENT(funobj) == scope) {
+      *rval = funobjVal;
+      return JS_TRUE;
+    }
+
+    JS_GetReservedSlot(cx, funobj, XPCWrapper::eWrappedFunctionSlot, &funobjVal);
+    funobj = JSVAL_TO_OBJECT(funobjVal);
   }
 
   JSFunction *funWrapper =
     JS_NewFunction(cx, XPC_COW_FunctionWrapper,
                    JS_GetFunctionArity(wrappedFun), 0,
-                   JS_GetGlobalForObject(cx, outerObj),
+                   scope,
                    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);
 }
 
 JSBool
-XPC_COW_RewrapForChrome(JSContext *cx, JSObject *wrapperObj, jsval *vp)
+RewrapForChrome(JSContext *cx, JSObject *wrapperObj, jsval *vp)
 {
   jsval v = *vp;
   if (JSVAL_IS_PRIMITIVE(v)) {
     return JS_TRUE;
   }
 
-  // We're rewrapping for chrome, so this is safe.
-  JSObject *obj = GetWrappedJSObject(cx, JSVAL_TO_OBJECT(v));
-  if (!obj) {
-    *vp = JSVAL_NULL;
-    return JS_TRUE;
-  }
-
-  XPCWrappedNative *wn;
-  JSBool ok;
-
-  // Set aside the frame chain so that we'll be able to wrap this object for
-  // chrome's use.
-  JSStackFrame *fp = JS_SaveFrameChain(cx);
-
-  if (IS_WN_WRAPPER(obj) &&
-      (wn = (XPCWrappedNative*)xpc_GetJSPrivate(obj)) &&
-      !nsXPCWrappedJSClass::IsWrappedJS(wn->Native())) {
-    // Return an explicit XPCNativeWrapper in case "chrome" code happens to be
-    // XBL code cloned into an untrusted context.
-    ok = XPCNativeWrapper::CreateExplicitWrapper(cx, wn, JS_TRUE, vp);
-  } else {
-    // Note: we're passing the wrapped chrome object as the scope for the SJOW.
-    ok = XPCSafeJSObjectWrapper::WrapObject(cx, GetWrappedObject(cx, wrapperObj),
-                                            *vp, vp);
-  }
-
-  JS_RestoreFrameChain(cx, fp);
-
-  return ok;
+  return RewrapObject(cx, JS_GetGlobalForObject(cx, GetWrappedObject(cx, wrapperObj)),
+                      JSVAL_TO_OBJECT(v), UNKNOWN, vp);
 }
 
 JSBool
-XPC_COW_RewrapForContent(JSContext *cx, JSObject *wrapperObj, jsval *vp)
+RewrapForContent(JSContext *cx, JSObject *wrapperObj, jsval *vp)
 {
   jsval v = *vp;
   if (JSVAL_IS_PRIMITIVE(v)) {
     return JS_TRUE;
   }
 
-  JSObject *obj = GetWrappedJSObject(cx, JSVAL_TO_OBJECT(v));
-  if (!obj) {
-    *vp = JSVAL_NULL;
-    return JS_TRUE;
+  JSObject *scope = JS_GetScopeChain(cx);
+  if (!scope) {
+    return JS_FALSE;
   }
 
-  if (JS_ObjectIsFunction(cx, obj)) {
-    return XPC_COW_WrapFunction(cx, wrapperObj, obj, vp);
-  }
-
-  return WrapObject(cx, JS_GetScopeChain(cx), OBJECT_TO_JSVAL(obj), vp);
+  return RewrapObject(cx, JS_GetGlobalForObject(cx, scope),
+                      JSVAL_TO_OBJECT(v), COW, vp);
 }
 
 static JSBool
 CheckSOW(JSContext *cx, JSObject *wrapperObj, jsval idval)
 {
   jsval flags;
   JS_GetReservedSlot(cx, wrapperObj, sFlagsSlot, &flags);
 
@@ -544,17 +514,17 @@ XPC_COW_AddProperty(JSContext *cx, JSObj
   if (desc.attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
     // Only chrome is allowed to add getters or setters to our object.
     // NB: We don't have to do this check again if we're already FLAG_SOW'd.
     if (!HAS_FLAGS(flags, FLAG_SOW) && !SystemOnlyWrapper::AllowedToAct(cx, id)) {
       return JS_FALSE;
     }
   }
 
-  return XPC_COW_RewrapForChrome(cx, obj, vp) &&
+  return RewrapForChrome(cx, obj, vp) &&
          JS_DefinePropertyById(cx, wrappedObj, interned_id, *vp,
                                desc.getter, desc.setter, desc.attrs);
 }
 
 static JSBool
 XPC_COW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   if (!CheckSOW(cx, obj, id)) {
@@ -627,28 +597,28 @@ XPC_COW_GetOrSetProperty(JSContext *cx, 
   if (!CanTouchProperty(cx, obj, interned_id, isSet, &canTouch)) {
     return JS_FALSE;
   }
 
   if (!canTouch) {
     return ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
   }
 
-  if (!XPC_COW_RewrapForChrome(cx, obj, vp)) {
+  if (isSet && !RewrapForChrome(cx, obj, vp)) {
     return JS_FALSE;
   }
 
   JSBool ok = isSet
               ? JS_SetPropertyById(cx, wrappedObj, interned_id, vp)
               : JS_GetPropertyById(cx, wrappedObj, interned_id, vp);
   if (!ok) {
     return JS_FALSE;
   }
 
-  return XPC_COW_RewrapForContent(cx, obj, vp);
+  return RewrapForContent(cx, obj, vp);
 }
 
 static JSBool
 XPC_COW_GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   return XPC_COW_GetOrSetProperty(cx, obj, id, vp, JS_FALSE);
 }
 
@@ -709,17 +679,18 @@ XPC_COW_NewResolve(JSContext *cx, JSObje
                         &canTouch)) {
     return JS_FALSE;
   }
 
   if (!canTouch) {
     return ThrowException(NS_ERROR_XPC_SECURITY_MANAGER_VETO, cx);
   }
 
-  return XPCWrapper::NewResolve(cx, obj, JS_TRUE, wrappedObj, id, flags, objp);
+  return XPCWrapper::NewResolve(cx, obj, JS_FALSE, wrappedObj, id, flags,
+                                objp);
 }
 
 static JSBool
 XPC_COW_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);
@@ -738,17 +709,17 @@ XPC_COW_Convert(JSContext *cx, JSObject 
   if (!ccx.IsValid()) {
     return ThrowException(NS_ERROR_FAILURE, cx);
   }
 
   if (!STOBJ_GET_CLASS(wrappedObj)->convert(cx, wrappedObj, type, vp)) {
     return JS_FALSE;
   }
 
-  return XPC_COW_RewrapForContent(cx, obj, vp);
+  return RewrapForContent(cx, obj, vp);
 }
 
 static JSBool
 XPC_COW_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.
--- a/js/src/xpconnect/src/XPCNativeWrapper.cpp
+++ b/js/src/xpconnect/src/XPCNativeWrapper.cpp
@@ -230,69 +230,43 @@ RewrapIfDeepWrapper(JSContext *cx, JSObj
   }
 
   jsval flags;
   ::JS_GetReservedSlot(cx, obj, 0, &flags);
 
   // Re-wrap non-primitive values if this is a deep wrapper, i.e.
   // if (HAS_FLAGS(flags, FLAG_DEEP).
   if (HAS_FLAGS(flags, FLAG_DEEP) && !primitive) {
-    // Unwrap a cross origin wrapper, since we're more restrictive.
-    if (STOBJ_GET_CLASS(nativeObj) == &XPCCrossOriginWrapper::XOWClass.base) {
-      if (!::JS_GetReservedSlot(cx, nativeObj, sWrappedObjSlot,
-                                &v)) {
+    JSObject *scope = JS_GetScopeChain(cx);
+    if (!scope) {
+      return JS_FALSE;
+    }
+
+    WrapperType type = HAS_FLAGS(flags, FLAG_EXPLICIT)
+                       ? XPCNW_EXPLICIT : XPCNW_IMPLICIT;
+
+    if (!RewrapObject(cx, JS_GetGlobalForObject(cx, scope),
+                      nativeObj, type, rval)) {
+      return JS_FALSE;
+    }
+  } else {
+    if (!JSVAL_IS_PRIMITIVE(v)) {
+      JSObject *scope = JS_GetScopeChain(cx);
+      if (!scope) {
         return JS_FALSE;
       }
 
-      // If v is primitive, allow nativeObj to remain a cross origin wrapper,
-      // which will fail below (since it isn't a wrapped native).
-      if (!JSVAL_IS_PRIMITIVE(v)) {
-        nativeObj = JSVAL_TO_OBJECT(v);
+      // NB: Because we're not a deep wrapper, we give a hint of SJOW to
+      // imitate not having a wrapper at all.
+      if (!RewrapObject(cx, JS_GetGlobalForObject(cx, scope),
+                        JSVAL_TO_OBJECT(v), SJOW, &v)) {
+        return JS_FALSE;
       }
     }
 
-    XPCWrappedNative* wrappedNative =
-      XPCWrappedNative::GetAndMorphWrappedNativeOfJSObject(cx, nativeObj);
-    if (!wrappedNative) {
-      return XPCSafeJSObjectWrapper::WrapObject(cx, JS_GetScopeChain(cx),
-                                                v, rval);
-    }
-
-    if (HAS_FLAGS(flags, FLAG_EXPLICIT)) {
-#ifdef DEBUG_XPCNativeWrapper
-      printf("Rewrapping for deep explicit wrapper\n");
-#endif
-      if (wrappedNative == XPCNativeWrapper::SafeGetWrappedNative(obj)) {
-        // Already wrapped, return the wrapper.
-        *rval = OBJECT_TO_JSVAL(obj);
-        return JS_TRUE;
-      }
-
-      // |obj| is an explicit deep wrapper.  We want to construct another
-      // explicit deep wrapper for |v|.
-
-      return XPCNativeWrapper::CreateExplicitWrapper(cx, wrappedNative,
-                                                     JS_TRUE, rval);
-    }
-
-#ifdef DEBUG_XPCNativeWrapper
-    printf("Rewrapping for deep implicit wrapper\n");
-#endif
-    // Just using GetNewOrUsed on the return value of
-    // GetWrappedNativeOfJSObject will give the right thing -- the unique deep
-    // implicit wrapper associated with wrappedNative.
-    JSObject* wrapperObj = XPCNativeWrapper::GetNewOrUsed(cx, wrappedNative,
-                                                          JS_GetScopeChain(cx),
-                                                          nsnull);
-    if (!wrapperObj) {
-      return JS_FALSE;
-    }
-
-    *rval = OBJECT_TO_JSVAL(wrapperObj);
-  } else {
     *rval = v;
   }
 
   return JS_TRUE;
 }
 
 } // namespace XPCNativeWrapper
 
@@ -455,16 +429,17 @@ EnsureLegalActivity(JSContext *cx, JSObj
   if (fileFlags == JSFILENAME_NULL || (fileFlags & JSFILENAME_SYSTEM)) {
     // We expect implicit native wrappers in system files.
     return JS_TRUE;
   }
 
   // Otherwise, we're looking at a non-system file with a handle on an
   // implicit wrapper. This is a bug! Deny access.
   NS_ERROR("Implicit native wrapper in content code");
+  return JS_FALSE;
 #else
   return JS_TRUE;
 #endif
 
   // NB: Watch for early returns in the ifdef DEBUG code above.
 }
 
 static JSBool
@@ -971,59 +946,59 @@ XPCNativeWrapperCtor(JSContext *cx, JSOb
     }
 
     *rval = native;
     return JS_TRUE;
   }
 
   JSObject *nativeObj = JSVAL_TO_OBJECT(native);
 
-  // Unwrap a cross origin wrapper, since we're more restrictive than it is.
-  JSObject *wrapper;
-  if ((wrapper = UnwrapGeneric(cx, &XPCCrossOriginWrapper::XOWClass, nativeObj))) {
-    nativeObj = wrapper;
-  } else if ((wrapper = UnwrapGeneric(cx, &XPCSafeJSObjectWrapper::SJOWClass, nativeObj))) {
-    nativeObj = wrapper;
+  // First, if this is another type of security wrapper, unwrap it to see what
+  // we're really dealing with.
+  nativeObj = UnsafeUnwrapSecurityWrapper(cx, nativeObj);
+  if (!nativeObj) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
   }
+  native = OBJECT_TO_JSVAL(nativeObj);
+
+  // Now, figure out if we're allowed to create an XPCNativeWrapper around it.
+  JSObject *scope = JS_GetScopeChain(cx);
+  if (!scope) {
+    return JS_FALSE;
+  }
+
+  XPCWrappedNativeScope *xpcscope =
+    XPCWrappedNativeScope::FindInJSObjectScope(cx, scope);
+  NS_ASSERTION(xpcscope, "what crazy scope are we in?");
 
   XPCWrappedNative *wrappedNative;
-
-  if (XPCNativeWrapper::IsNativeWrapper(nativeObj)) {
-    // We're asked to wrap an already wrapped object. Re-wrap the
-    // object wrapped by the given wrapper.
-
-#ifdef DEBUG_XPCNativeWrapper
-    printf("Wrapping already wrapped object\n");
-#endif
+  WrapperType type = xpcscope->GetWrapperFor(cx, nativeObj, XPCNW_EXPLICIT,
+                                             &wrappedNative);
 
-    // It's always safe to re-wrap an object.
-    wrappedNative = XPCNativeWrapper::SafeGetWrappedNative(nativeObj);
+  if (type != NONE && !(type & XPCNW_EXPLICIT)) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
+  }
 
-    if (!wrappedNative) {
-      return ThrowException(NS_ERROR_INVALID_ARG, cx);
-    }
-
-    nativeObj = wrappedNative->GetFlatJSObject();
-    native = OBJECT_TO_JSVAL(nativeObj);
-  } else {
+  // We might have to morph.
+  if (!wrappedNative) {
     wrappedNative =
       XPCWrappedNative::GetAndMorphWrappedNativeOfJSObject(cx, nativeObj);
 
     if (!wrappedNative) {
       return ThrowException(NS_ERROR_INVALID_ARG, cx);
     }
+  }
 
-    // Prevent wrapping a double-wrapped JS object in an
-    // XPCNativeWrapper!
-    nsCOMPtr<nsIXPConnectWrappedJS> xpcwrappedjs =
-      do_QueryWrappedNative(wrappedNative);
+  // Prevent wrapping a double-wrapped JS object in an
+  // XPCNativeWrapper!
+  nsCOMPtr<nsIXPConnectWrappedJS> xpcwrappedjs =
+    do_QueryWrappedNative(wrappedNative);
 
-    if (xpcwrappedjs) {
-      return ThrowException(NS_ERROR_INVALID_ARG, cx);
-    }
+  if (xpcwrappedjs) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
   }
 
   PRBool hasStringArgs = PR_FALSE;
   for (uintN i = 1; i < argc; ++i) {
     if (!JSVAL_IS_STRING(argv[i])) {
       hasStringArgs = PR_FALSE;
 
       break;
@@ -1052,18 +1027,26 @@ XPCNativeWrapperCtor(JSContext *cx, JSOb
       return ThrowException(NS_ERROR_UNEXPECTED, cx);
     }
 
     if (!hasInstance) {
       return ThrowException(NS_ERROR_INVALID_ARG, cx);
     }
   }
 
-  return XPCNativeWrapper::CreateExplicitWrapper(cx, wrappedNative,
-                                                 !hasStringArgs, rval);
+  if (!XPCNativeWrapper::CreateExplicitWrapper(cx, wrappedNative,
+                                               !hasStringArgs, rval)) {
+    return JS_FALSE;
+  }
+
+  if (!(type & SOW)) {
+    return JS_TRUE;
+  }
+
+  return SystemOnlyWrapper::MakeSOW(cx, JSVAL_TO_OBJECT(*rval));
 }
 
 static void
 XPC_NW_Trace(JSTracer *trc, JSObject *obj)
 {
   // Untrusted code can't trigger this.
   XPCWrappedNative *wrappedNative = XPCNativeWrapper::SafeGetWrappedNative(obj);
 
--- a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp
+++ b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp
@@ -89,28 +89,28 @@ static JSBool
 XPC_SJOW_Equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
 
 static JSObject *
 XPC_SJOW_Iterator(JSContext *cx, JSObject *obj, JSBool keysonly);
 
 static JSObject *
 XPC_SJOW_WrappedObject(JSContext *cx, JSObject *obj);
 
+using namespace XPCSafeJSObjectWrapper;
+using namespace XPCWrapper;
+
 static inline
 JSBool
 ThrowException(nsresult ex, JSContext *cx)
 {
-  XPCThrower::Throw(ex, cx);
+  DoThrowException(ex, cx);
 
   return JS_FALSE;
 }
 
-using namespace XPCSafeJSObjectWrapper;
-using namespace XPCWrapper;
-
 // Find the subject and object principal. The argument
 // subjectPrincipal can be null if the caller doesn't care about the
 // subject principal, and secMgr can also be null if the caller
 // doesn't need the security manager.
 static nsresult
 FindPrincipals(JSContext *cx, JSObject *obj, nsIPrincipal **objectPrincipal,
                nsIPrincipal **subjectPrincipal,
                nsIScriptSecurityManager **secMgr,
@@ -269,18 +269,69 @@ JSExtendedClass SJOWClass = {
   XPC_SJOW_Iterator,
   XPC_SJOW_WrappedObject,
   JSCLASS_NO_RESERVED_MEMBERS
 };
 
 JSBool
 WrapObject(JSContext *cx, JSObject *scope, jsval v, jsval *vp)
 {
-  *vp = v;
-  return XPC_SJOW_Construct(cx, scope, 1, vp, vp);
+  // This might be redundant if called from XPC_SJOW_Construct, but it should
+  // be cheap in that case.
+  JSObject *objToWrap = UnsafeUnwrapSecurityWrapper(cx, JSVAL_TO_OBJECT(v));
+  if (!objToWrap) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
+  }
+
+  // Prevent script created Script objects from ever being wrapped
+  // with XPCSafeJSObjectWrapper, and never let the eval function
+  // object be directly wrapped.
+
+  if (STOBJ_GET_CLASS(objToWrap) == &js_ScriptClass ||
+      (JS_ObjectIsFunction(cx, objToWrap) &&
+       JS_GetFunctionFastNative(cx, JS_ValueToFunction(cx, v)) ==
+       XPCWrapper::sEvalNative)) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
+  }
+
+  XPCWrappedNativeScope *xpcscope =
+    XPCWrappedNativeScope::FindInJSObjectScope(cx, scope);
+  NS_ASSERTION(xpcscope, "what crazy scope are we in?");
+
+  XPCWrappedNative *wrappedNative;
+  WrapperType type = xpcscope->GetWrapperFor(cx, objToWrap, SJOW,
+                                             &wrappedNative);
+
+  // NB: We allow XOW here because we're as restrictive as it is (and we know
+  // we're same origin here).
+  if (type != NONE && type != XOW && !(type & SJOW)) {
+    return ThrowException(NS_ERROR_INVALID_ARG, cx);
+  }
+
+  SLIM_LOG_WILL_MORPH(cx, objToWrap);
+  if (IS_SLIM_WRAPPER(objToWrap) && !MorphSlimWrapper(cx, objToWrap)) {
+    return ThrowException(NS_ERROR_FAILURE, cx);
+  }
+
+  JSObject *wrapperObj =
+    JS_NewObjectWithGivenProto(cx, &SJOWClass.base, nsnull, scope);
+
+  if (!wrapperObj) {
+    // JS_NewObjectWithGivenProto already threw.
+    return JS_FALSE;
+  }
+
+  *vp = OBJECT_TO_JSVAL(wrapperObj);
+  if (!JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sWrappedObjSlot,
+                          OBJECT_TO_JSVAL(objToWrap)) ||
+      !JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sFlagsSlot, JSVAL_ZERO)) {
+    return JS_FALSE;
+  }
+
+  return JS_TRUE;
 }
 
 PRBool
 AttachNewConstructorObject(XPCCallContext &ccx, JSObject *aGlobalObject)
 {
   // Initialize sEvalNative the first time we attach a constructor.
   // NB: This always happens before any cross origin wrappers are
   // created, so it's OK to do this here.
@@ -342,31 +393,26 @@ GetUnsafeObject(JSContext *cx, JSObject 
 static JSBool
 WrapJSValue(JSContext *cx, JSObject *obj, jsval val, jsval *rval)
 {
   JSBool ok = JS_TRUE;
 
   if (JSVAL_IS_PRIMITIVE(val)) {
     *rval = val;
   } else {
+    if (!RewrapObject(cx, STOBJ_GET_PARENT(obj), JSVAL_TO_OBJECT(val), SJOW,
+                      rval)) {
+      return JS_FALSE;
+    }
     // Construct a new safe wrapper. Note that it doesn't matter what
     // parent we pass in here, the construct hook will ensure we get
     // the right parent for the wrapper.
-    JSObject *safeObj =
-      ::JS_ConstructObjectWithArguments(cx, &SJOWClass.base, nsnull,
-                                        nsnull, 1, &val);
-    if (!safeObj) {
-      return JS_FALSE;
-    }
-
-    // Set *rval to safeObj here to ensure it doesn't get collected in
-    // any of the code below.
-    *rval = OBJECT_TO_JSVAL(safeObj);
-
-    if (JS_GetGlobalForObject(cx, obj) != JS_GetGlobalForObject(cx, safeObj)) {
+    JSObject *safeObj = JSVAL_TO_OBJECT(*rval);
+    if (STOBJ_GET_CLASS(safeObj) == &SJOWClass.base &&
+        JS_GetGlobalForObject(cx, obj) != JS_GetGlobalForObject(cx, safeObj)) {
       // Check to see if the new object we just wrapped is accessible
       // from the unsafe object we got the new object through. If not,
       // force the new wrapper to use the principal of the unsafe
       // object we got the new object from.
       nsCOMPtr<nsIPrincipal> srcObjPrincipal;
       nsCOMPtr<nsIPrincipal> subjPrincipal;
       nsCOMPtr<nsIPrincipal> valObjPrincipal;
 
@@ -418,31 +464,16 @@ WrapJSValue(JSContext *cx, JSObject *obj
         srcObjPrincipal.swap(tmp);
       }
     }
   }
 
   return ok;
 }
 
-static jsval
-UnwrapJSValue(JSContext *cx, jsval val)
-{
-  if (JSVAL_IS_PRIMITIVE(val)) {
-    return val;
-  }
-
-  JSObject *unsafeObj = GetUnsafeObject(cx, JSVAL_TO_OBJECT(val));
-  if (unsafeObj) {
-    return OBJECT_TO_JSVAL(unsafeObj);
-  }
-
-  return val;
-}
-
 static JSBool
 XPC_SJOW_AddProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
 {
   // The constructor and toString properties needs to live on the safe
   // wrapper.
   if (id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_CONSTRUCTOR) ||
       id == GetRTStringByIndex(cx, XPCJSRuntime::IDX_TO_STRING)) {
     return JS_TRUE;
@@ -465,16 +496,26 @@ XPC_SJOW_AddProperty(JSContext *cx, JSOb
   }
 
   // Check that the caller can access the unsafe object.
   if (!CanCallerAccess(cx, obj, unsafeObj)) {
     // CanCallerAccess() already threw for us.
     return JS_FALSE;
   }
 
+  if (!JSVAL_IS_PRIMITIVE(*vp)) {
+    // Adding an object of some type to the content object, make sure it's
+    // properly wrapped.
+    JSObject *added = JSVAL_TO_OBJECT(*vp);
+    if (!RewrapObject(cx, JS_GetGlobalForObject(cx, unsafeObj), added,
+                      UNKNOWN, vp)) {
+      return JS_FALSE;
+    }
+  }
+
   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(cx, obj);
   if (!unsafeObj) {
@@ -566,18 +607,21 @@ XPC_SJOW_GetOrSetProperty(JSContext *cx,
       return JS_FALSE;
     }
 
     jsid interned_id;
     if (!JS_ValueToId(cx, id, &interned_id)) {
       return JS_FALSE;
     }
 
-    if (aIsSet) {
-      *vp = UnwrapJSValue(cx, *vp);
+    if (aIsSet &&
+        !JSVAL_IS_PRIMITIVE(*vp) &&
+        !RewrapObject(cx, JS_GetGlobalForObject(cx, unsafeObj),
+                      JSVAL_TO_OBJECT(*vp), UNKNOWN, vp)) {
+      return JS_FALSE;
     }
 
     JSBool ok = aIsSet
                 ? JS_SetPropertyById(cx, unsafeObj, interned_id, vp)
                 : JS_GetPropertyById(cx, unsafeObj, interned_id, vp);
     if (!ok) {
       return JS_FALSE;
     }
@@ -652,17 +696,18 @@ 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, JS_FALSE, 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;
 }
@@ -773,21 +818,31 @@ XPC_SJOW_Call(JSContext *cx, JSObject *o
       !CanCallerAccess(cx, nsnull, funToCall)) {
     // CanCallerAccess() already threw for us.
     return JS_FALSE;
   }
 
   {
     SafeCallGuard guard(cx, FindObjectPrincipals(cx, safeObj, funToCall));
 
+    JSObject *scope = JS_GetGlobalForObject(cx, funToCall);
     for (uintN i = 0; i < argc; ++i) {
-      argv[i] = UnwrapJSValue(cx, argv[i]);
+      // NB: Passing NONE for a hint here.
+      if (!JSVAL_IS_PRIMITIVE(argv[i]) &&
+          !RewrapObject(cx, scope, JSVAL_TO_OBJECT(argv[i]), NONE, &argv[i])) {
+        return JS_FALSE;
+      }
     }
 
-    if (!JS_CallFunctionValue(cx, callThisObj, OBJECT_TO_JSVAL(funToCall),
+    jsval v;
+    if (!RewrapObject(cx, scope, callThisObj, NONE, &v)) {
+      return JS_FALSE;
+    }
+
+    if (!JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(v), OBJECT_TO_JSVAL(funToCall),
                               argc, argv, rval)) {
       return JS_FALSE;
     }
   }
 
   return WrapJSValue(cx, obj, *rval, rval);
 }
 
@@ -809,65 +864,28 @@ XPC_SJOW_Construct(JSContext *cx, JSObje
     if (JS_FrameIterator(cx, &fp) && JS_IsConstructorFrame(cx, fp)) {
       return ThrowException(NS_ERROR_ILLEGAL_VALUE, cx);
     }
 
     *rval = argv[0];
     return JS_TRUE;
   }
 
-  JSObject *objToWrap = JSVAL_TO_OBJECT(argv[0]);
-
-  // Prevent script created Script objects from ever being wrapped
-  // with XPCSafeJSObjectWrapper, and never let the eval function
-  // object be directly wrapped.
-
-  if (STOBJ_GET_CLASS(objToWrap) == &js_ScriptClass ||
-      (::JS_ObjectIsFunction(cx, objToWrap) &&
-       ::JS_GetFunctionFastNative(cx, ::JS_ValueToFunction(cx, argv[0])) ==
-       XPCWrapper::sEvalNative)) {
+  JSObject *objToWrap = UnsafeUnwrapSecurityWrapper(cx, JSVAL_TO_OBJECT(argv[0]));
+  if (!objToWrap) {
     return ThrowException(NS_ERROR_INVALID_ARG, cx);
   }
 
-  SLIM_LOG_WILL_MORPH(cx, objToWrap);
-  if (IS_SLIM_WRAPPER(objToWrap) && !MorphSlimWrapper(cx, objToWrap)) {
-    return ThrowException(NS_ERROR_FAILURE, cx);
-  }
-
   // Check that the caller can access the unsafe object.
   if (!CanCallerAccess(cx, nsnull, objToWrap)) {
     // CanCallerAccess() already threw for us.
     return JS_FALSE;
   }
 
-  JSObject *unsafeObj = GetUnsafeObject(cx, objToWrap);
-
-  if (unsafeObj) {
-    // We're asked to wrap an already wrapped object. Re-wrap the
-    // object wrapped by the given wrapper.
-
-    objToWrap = unsafeObj;
-  }
-
-  JSObject *wrapperObj =
-    JS_NewObjectWithGivenProto(cx, &SJOWClass.base, nsnull, scope);
-
-  if (!wrapperObj) {
-    // JS_NewObjectWithGivenProto already threw.
-    return JS_FALSE;
-  }
-
-  *rval = OBJECT_TO_JSVAL(wrapperObj);
-  if (!JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sWrappedObjSlot,
-                          OBJECT_TO_JSVAL(objToWrap)) ||
-      !JS_SetReservedSlot(cx, wrapperObj, XPCWrapper::sFlagsSlot, JSVAL_ZERO)) {
-    return JS_FALSE;
-  }
-
-  return JS_TRUE;
+  return WrapObject(cx, scope, OBJECT_TO_JSVAL(objToWrap), rval);
 }
 
 static JSBool
 XPC_SJOW_Create(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                 jsval *rval)
 {
   JSObject *callee = JSVAL_TO_OBJECT(argv[-2]);
   NS_ASSERTION(GetUnsafeObject(cx, callee), "How'd we get here?");
@@ -880,17 +898,31 @@ XPC_SJOW_Create(JSContext *cx, JSObject 
   }
 
   {
     SafeCallGuard guard(cx, FindObjectPrincipals(cx, callee, unsafeObj));
     if (!guard.ready()) {
       return JS_FALSE;
     }
 
-    if (!JS_CallFunctionValue(cx, obj, OBJECT_TO_JSVAL(callee),
+    JSObject *scope = JS_GetGlobalForObject(cx, unsafeObj);
+    for (uintN i = 0; i < argc; ++i) {
+      // NB: Passing NONE for a hint here.
+      if (!JSVAL_IS_PRIMITIVE(argv[i]) &&
+          !RewrapObject(cx, scope, JSVAL_TO_OBJECT(argv[i]), NONE, &argv[i])) {
+        return JS_FALSE;
+      }
+    }
+
+    jsval v;
+    if (!RewrapObject(cx, scope, obj, NONE, &v)) {
+      return JS_FALSE;
+    }
+
+    if (!JS_CallFunctionValue(cx, JSVAL_TO_OBJECT(v), OBJECT_TO_JSVAL(unsafeObj),
                               argc, argv, rval)) {
       return JS_FALSE;
     }
   }
 
   return WrapJSValue(cx, callee, *rval, rval);
 }
 
@@ -939,30 +971,16 @@ XPC_SJOW_Iterator(JSContext *cx, JSObjec
   }
 
   // Check that the caller can access the unsafe object.
   if (!CanCallerAccess(cx, obj, unsafeObj)) {
     // CanCallerAccess() already threw for us.
     return nsnull;
   }
 
-  JSObject *tmp =
-    XPCWrapper::UnwrapGeneric(cx, &XPCCrossOriginWrapper::XOWClass,
-                              unsafeObj);
-  if (tmp) {
-    unsafeObj = tmp;
-
-    // Repeat the CanCallerAccess check because the XOW is parented to our
-    // scope's global object which makes the above CanCallerAccess call lie.
-    if (!CanCallerAccess(cx, nsnull, unsafeObj)) {
-      // CanCallerAccess() already threw for us.
-      return nsnull;
-    }
-  }
-
   // Create our dummy SJOW.
   JSObject *wrapperIter =
     JS_NewObjectWithGivenProto(cx, &SJOWClass.base, nsnull,
                                JS_GetGlobalForObject(cx, obj));
   if (!wrapperIter) {
     return nsnull;
   }
 
--- a/js/src/xpconnect/src/XPCSystemOnlyWrapper.cpp
+++ b/js/src/xpconnect/src/XPCSystemOnlyWrapper.cpp
@@ -550,17 +550,17 @@ XPC_SOW_NewResolve(JSContext *cx, JSObje
     *objp = nsnull;
     return JS_TRUE;
   }
 
   if (!AllowedToAct(cx, id)) {
     return JS_FALSE;
   }
 
-  return NewResolve(cx, obj, JS_TRUE, wrappedObj, id, flags, objp);
+  return NewResolve(cx, obj, JS_FALSE, 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;
   }
--- a/js/src/xpconnect/src/XPCWrapper.cpp
+++ b/js/src/xpconnect/src/XPCWrapper.cpp
@@ -176,45 +176,59 @@ static JSClass IteratorClass = {
 
   JSCLASS_NO_OPTIONAL_MEMBERS
 };
 
 JSBool
 RewrapObject(JSContext *cx, JSObject *scope, JSObject *obj, WrapperType hint,
              jsval *vp)
 {
-  if (IsSecurityWrapper(obj)) {
-    jsval v;
-    JS_GetReservedSlot(cx, obj, sWrappedObjSlot, &v);
-    NS_ASSERTION(!JSVAL_IS_PRIMITIVE(v), "bad object");
-    obj = JSVAL_TO_OBJECT(v);
-  } else if (XPCNativeWrapper::IsNativeWrapper(obj)) {
-    XPCWrappedNative *wn = XPCNativeWrapper::SafeGetWrappedNative(obj);
-    if (!wn) {
-      *vp = JSVAL_NULL;
-      return JS_TRUE;
-    }
-
-    obj = wn->GetFlatJSObject();
+  obj = UnsafeUnwrapSecurityWrapper(cx, obj);
+  if (!obj) {
+    // A wrapper wrapping NULL (such as XPCNativeWrapper.prototype).
+    *vp = JSVAL_NULL;
+    return JS_TRUE;
   }
 
   XPCWrappedNativeScope *nativescope =
     XPCWrappedNativeScope::FindInJSObjectScope(cx, scope);
   XPCWrappedNative *wn;
   WrapperType answer = nativescope->GetWrapperFor(cx, obj, hint, &wn);
 
   *vp = OBJECT_TO_JSVAL(obj);
   if (answer == NONE) {
     return JS_TRUE;
   }
 
 
   return CreateWrapperFromType(cx, scope, wn, answer, vp);
 }
 
+JSObject *
+UnsafeUnwrapSecurityWrapper(JSContext *cx, JSObject *obj)
+{
+  if (IsSecurityWrapper(obj)) {
+    jsval v;
+    JS_GetReservedSlot(cx, obj, sWrappedObjSlot, &v);
+    NS_ASSERTION(!JSVAL_IS_PRIMITIVE(v), "bad object");
+    return JSVAL_TO_OBJECT(v);
+  }
+
+  if (XPCNativeWrapper::IsNativeWrapper(obj)) {
+    XPCWrappedNative *wn = XPCNativeWrapper::SafeGetWrappedNative(obj);
+    if (!wn) {
+      return nsnull;
+    }
+
+    return wn->GetFlatJSObject();
+  }
+
+  return obj;
+}
+
 JSBool
 CreateWrapperFromType(JSContext *cx, JSObject *scope, XPCWrappedNative *wn,
                       WrapperType hint, jsval *vp)
 {
 #ifdef DEBUG
   NS_ASSERTION(!wn || wn->GetFlatJSObject() == JSVAL_TO_OBJECT(*vp),
                "bad wrapped native");
 #endif
--- a/js/src/xpconnect/src/XPCWrapper.h
+++ b/js/src/xpconnect/src/XPCWrapper.h
@@ -419,16 +419,19 @@ WrapFunction(JSContext *cx, JSObject *wr
 
 /**
  * Given a potentially-wrapped object, creates a wrapper for it.
  */
 JSBool
 RewrapObject(JSContext *cx, JSObject *scope, JSObject *obj, WrapperType hint,
              jsval *vp);
 
+JSObject *
+UnsafeUnwrapSecurityWrapper(JSContext *cx, JSObject *obj);
+
 JSBool
 CreateWrapperFromType(JSContext *cx, JSObject *scope, XPCWrappedNative *wn,
                       WrapperType hint, jsval *vp);
 
 /**
  * Creates an iterator object that walks up the prototype of
  * wrappedObj. This is suitable for for-in loops over a wrapper. If
  * a property is not supposed to be reflected, the resolve hook
@@ -458,21 +461,24 @@ DelProperty(JSContext *cx, JSObject *obj
  */
 JSBool
 Enumerate(JSContext *cx, JSObject *wrapperObj, 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.
+ *
+ * If |caller| is not NONE, then we will call the proper WrapObject
+ * hook for any getters or setters about to be lifted onto
+ * |wrapperObj|.
  */
 JSBool
-NewResolve(JSContext *cx, JSObject *wrapperObj,
-           JSBool preserveVal, JSObject *innerObj,
-           jsval id, uintN flags, JSObject **objp);
+NewResolve(JSContext *cx, JSObject *wrapperObj, 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.
  */
 JSBool
 ResolveNativeProperty(JSContext *cx, JSObject *wrapperObj,
--- a/js/src/xpconnect/src/xpcwrappednativescope.cpp
+++ b/js/src/xpconnect/src/xpcwrappednativescope.cpp
@@ -1048,25 +1048,29 @@ XPCWrappedNativeScope::GetWrapperFor(JSC
         // principal-changing objects (like window and location objects)
         // changing. They are identified by ClassNeedsXOW.
         // But note: we don't want to create XOWs in chrome code, so just
         // use a SJOW, which does the Right Thing.
         JSBool wantsXOW =
             XPCCrossOriginWrapper::ClassNeedsXOW(obj->getClass()->name);
 
         // Is other a chrome object?
+        JSObject *obj2;
+        XPCWrappedNative *wrapper =
+            XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj, nsnull, &obj2);
         if(principalEqual || obj->isSystem())
-            return (hint & XPCNW) ? hint : wantsXOW ? SJOW : NONE;
+        {
+            if(hint & XPCNW)
+                return (wrapper || obj2) ? hint : NONE;
+            return wantsXOW ? SJOW : NONE;
+        }
 
         // Other isn't a chrome object: we need to wrap it in a SJOW or an
         // XPCNW.
 
-        JSObject *obj2;
-        XPCWrappedNative *wrapper =
-            XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj, nsnull, &obj2);
         if(!wrapper && !obj2)
             hint = SJOW;
 
         if(wn)
             *wn = wrapper;
         if(hint == UNKNOWN)
             hint = XPCNW_IMPLICIT;
 
@@ -1100,19 +1104,23 @@ XPCWrappedNativeScope::GetWrapperFor(JSC
 
         return COW;
     }
 
     // If this object isn't an XPCWrappedNative, then we don't need to create
     // any other types of wrapper than the hint.
     if(!wrapper && !obj2)
     {
-        NS_ASSERTION(principalEqual,
+#if 0
+        // XXX Re-enable these assertions when we have a better mochitest
+        // solution than UniversalXPConnect.
+        NS_ASSERTION(principalEqual || hint == COW,
                      "touching non-wrappednative object cross origin?");
-        NS_ASSERTION(hint == SJOW || hint == UNKNOWN, "bad hint");
+        NS_ASSERTION(hint == SJOW || hint == COW || hint == UNKNOWN, "bad hint");
+#endif
         return hint;
     }
 
     // NB: obj2 controls whether or not this is actually a "wrapped native".
     if(wrapper)
     {
         if(wrapper->NeedsChromeWrapper())
             return WrapperType(SOW | (hint & (SJOW | XPCNW_EXPLICIT | COW)));
@@ -1127,14 +1135,17 @@ XPCWrappedNativeScope::GetWrapperFor(JSC
 #endif
             return COW; // NB: Ignore hint.
         }
     }
 
     if(!principalEqual ||
        XPCCrossOriginWrapper::ClassNeedsXOW(obj->getClass()->name))
     {
-        NS_ASSERTION(hint != SJOW, "shouldn't have a SJOW for cross origin access?");
+        // NB: We want to assert that hint is not SJOW here, but it can
+        // be because of shallow XPCNativeWrappers. In that case, XOW is
+        // the right return value because XPCNativeWrappers are meant for
+        // chrome, and we're in content which shouldn't expect SJOWs.
         return (hint & XPCNW) ? XPCNW_EXPLICIT : XOW;
     }
 
     return (hint & XPCNW) ? XPCNW_EXPLICIT : (hint == SJOW) ? SJOW : NONE;
 }
--- a/js/src/xpconnect/tests/chrome/Makefile.in
+++ b/js/src/xpconnect/tests/chrome/Makefile.in
@@ -43,12 +43,13 @@ relativesrcdir  = js/src/xpconnect/tests
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _CHROME_FILES = \
 		test_bug500931.xul \
 		bug503926.xul \
 		test_bug503926.xul \
 		test_bug533596.xul \
+		test_wrappers.xul \
 		$(NULL)
 
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
--- a/js/src/xpconnect/tests/chrome/bug503926.xul
+++ b/js/src/xpconnect/tests/chrome/bug503926.xul
@@ -13,16 +13,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
       var passed = false;
       var obj = { QueryInterface: function() { passed = true; } }
       try { document.documentElement.appendChild(obj); } catch (e) {}
-      var isDialog = location.hash != 'iframe';
-      var outer = isDialog ? opener.wrappedJSObject : top.wrappedJSObject;
-      outer.ok(passed, "chrome/chrome test passed");
+      var isDialog = location.hash != '#iframe';
+      var outer = XPCNativeWrapper.unwrap(isDialog ? opener : top);
+      outer.ok(passed, "chrome/chrome test passed: " + (isDialog ? "dialog" : "iframe"));
       if (isDialog)
         close();
   ]]>
   </script>
 </window>
--- a/js/src/xpconnect/tests/chrome/test_bug503926.xul
+++ b/js/src/xpconnect/tests/chrome/test_bug503926.xul
@@ -10,33 +10,33 @@ https://bugzilla.mozilla.org/show_bug.cg
           src="chrome://mochikit/content/MochiKit/packed.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=503926"
      target="_blank">Mozilla Bug 503926</a>
-  </body>
 
   <iframe id="ifr" type="content" onload="iframe_loaded()"
-          href="chrome://mochikit/content/chrome/js/src/xpconnect/tests/chrome/bug503926.xul#iframe"/>
+          src="chrome://mochikit/content/chrome/js/src/xpconnect/tests/chrome/bug503926.xul#iframe"/>
+  </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
 
   /** Test for Bug 503926 **/
   function iframe_loaded() {
     var passed = false;
     var obj = { QueryInterface: function() { passed = true; } };
     try { document.documentElement.appendChild(obj); } catch (e) {}
     ok(passed, "trusted QIs should be called");
 
     openDialog("chrome://mochikit/content/chrome/js/src/xpconnect/tests/chrome/bug503926.xul",
-               "chromeDialog", "modal")
+               "chromeDialog", "modal");
     SimpleTest.finish();
   }
 
   SimpleTest.waitForExplicitFinish();
   ]]>
   </script>
 </window>
--- a/js/src/xpconnect/tests/chrome/test_bug533596.xul
+++ b/js/src/xpconnect/tests/chrome/test_bug533596.xul
@@ -27,17 +27,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     try { XPCNativeWrapper.unwrap(); } catch (e) {}
     try { XPCNativeWrapper.unwrap(0); } catch (e) {}
     try { XPCNativeWrapper.unwrap(null); } catch (e) {}
 
     var o = {};
     is(o, XPCNativeWrapper.unwrap(o), "unwrap on a random object returns it");
 
     var win = $('ifr').contentWindow;
-    var utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
     is(utils.getClassName(win), "XPCNativeWrapper", "win is an XPCNW");
     ok("x" in XPCNativeWrapper.unwrap(win), "actually unwrapped");
     is(utils.getClassName(XPCNativeWrapper.unwrap(win)), "XPCSafeJSObjectWrapper",
        "unwrap on an NW returns the same object");
     is(utils.getClassName(XPCNativeWrapper.unwrap(new XPCNativeWrapper(win))), "XPCSafeJSObjectWrapper",
        "unwrap on an explicit NW works too");
 
     ok(utils.getClassName(window) !== "XPCNativeWrapper", "window is not a native wrapper");
new file mode 100644
--- /dev/null
+++ b/js/src/xpconnect/tests/chrome/test_wrappers.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=500931
+-->
+<window title="Mozilla Bug 500931"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533596"
+     target="_blank">Mozilla Bug 533596</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+
+  /** Test for Bug 533596 **/
+
+  function go() {
+    var win = $('ifr').contentWindow;
+    var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    todo_is(utils.getClassName(window), "XPCSafeJSObjectWrapper", "our window is wrapped correctly")
+    todo_is(utils.getClassName(location), "XPCSafeJSObjectWrapper", "our location is wrapped correctly")
+    is(utils.getClassName(win), "XPCNativeWrapper", "win is an XPCNW");
+    is(utils.getClassName(win.location), "XPCNativeWrapper", "deep wrapping works");
+    is(win.location.href, "http://example.org/tests/js/src/xpconnect/tests/mochitest/chrome_wrappers_helper.html",
+       "can still get strings out");
+
+    var unsafeWin = win.wrappedJSObject;
+    is(utils.getClassName(unsafeWin), "XPCSafeJSObjectWrapper", "can get a SJOW");
+    is(utils.getClassName(unsafeWin.location), "XPCSafeJSObjectWrapper", "deep wrapping works");
+
+    unsafeWin.run_test(ok, win, unsafeWin);
+
+    win.setTimeout(function() {
+                       is(utils.getClassName(this), "XPCNativeWrapper",
+                          "this is wrapped correctly");
+                       SimpleTest.finish();
+                   }, 0)
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  ]]></script>
+  <iframe type="content"
+          src="http://example.org/tests/js/src/xpconnect/tests/mochitest/chrome_wrappers_helper.html"
+          onload="go()"
+          id="ifr">
+  </iframe>
+</window>
--- a/js/src/xpconnect/tests/mochitest/Makefile.in
+++ b/js/src/xpconnect/tests/mochitest/Makefile.in
@@ -43,16 +43,17 @@ relativesrcdir  = js/src/xpconnect/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =	bug500931_helper.html \
 		inner.html \
 		bug92773_helper.html \
 		bug504877_helper.html \
+		chrome_wrappers_helper.html \
 		test_bug92773.html \
 		test_bug361111.xul \
 		test_bug384632.html \
 		test_bug390488.html \
 		test_bug393269.html \
 		test_bug396851.html \
 		test_bug428021.html \
 		test_bug448587.html \
new file mode 100644
--- /dev/null
+++ b/js/src/xpconnect/tests/mochitest/chrome_wrappers_helper.html
@@ -0,0 +1,21 @@
+<html>
+    <head>
+        <script>
+            function check_wrapper(ok, wrapper, expected, note) {
+                netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+                var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                                  .getInterface(Components.interfaces.nsIDOMWindowUtils);
+                ok(utils.getClassName(wrapper) === expected, note);
+            }
+            function run_test(ok, xpcnw, sjow) {
+                // both wrappers should point to our window: XOW
+                check_wrapper(ok, ok, "Function", "functions are wrapped properly")
+                ok(ok.__parent__ == window, "ok is parented correctly");
+                check_wrapper(ok, xpcnw, "XPCCrossOriginWrapper", "XPCNWs are transformed correctly");
+                check_wrapper(ok, sjow, "XPCCrossOriginWrapper", "SJOWs are transformed correctly");
+            }
+        </script>
+    </head>
+    <body>
+    </body>
+</html>
--- a/js/src/xpconnect/tests/mochitest/test_cows.html
+++ b/js/src/xpconnect/tests/mochitest/test_cows.html
@@ -144,16 +144,23 @@ function COWTests() {
     try {
         var COWFunc = getCOW((function() { return 5; }));
         is(COWFunc(), 5, "COWed functions should be callable");
     } catch (e) {
         todo(false, "COWed functions should not raise " + e);
     }
 
     try {
+        var obj = {
+            get prop() { return { __exposedProps__: {}, test: "FAIL" } }
+        };
+        ok(getCOW(obj).prop.test != "FAIL", "getting prop.test should throw");
+    } catch (e) {}
+
+    try {
         var objWithFunc = {__exposedProps__: {foo: 'r'},
                            foo: function foo() { return 5; }};
         is(getCOW((objWithFunc)).foo(), 5,
            "Readable function exposed props should be callable");
     } catch (e) {
         ok(false, "Readable function exposed props should be callable" + e);
     }