Bug 926012 - Part 3: Convert wrappers to using dynamic prototype hooks. (r=bholley)
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Dec 2013 12:01:30 -0800
changeset 177473 ae50af3377666ac4f78992828e5ea38590fa2b29
parent 177472 8ba79063973d486a5f32d7f8bb67a22523399705
child 177474 ac58cfd40672895c6c9dcd28e5d65f00cccde6b7
push id462
push userraliiev@mozilla.com
push dateTue, 22 Apr 2014 00:22:30 +0000
treeherdermozilla-release@ac5db8c74ac0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs926012
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 926012 - Part 3: Convert wrappers to using dynamic prototype hooks. (r=bholley)
dom/base/nsGlobalWindow.cpp
js/src/jsapi-tests/testBug604087.cpp
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/shell/js.cpp
js/xpconnect/wrappers/WaiveXrayWrapper.cpp
js/xpconnect/wrappers/WaiveXrayWrapper.h
js/xpconnect/wrappers/WrapperFactory.cpp
js/xpconnect/wrappers/WrapperFactory.h
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1006,21 +1006,17 @@ nsChromeOuterWindowProxy::className(JSCo
 
 nsChromeOuterWindowProxy
 nsChromeOuterWindowProxy::singleton;
 
 static JSObject*
 NewOuterWindowProxy(JSContext *cx, JS::Handle<JSObject*> parent, bool isChrome)
 {
   JSAutoCompartment ac(cx, parent);
-  JS::Rooted<JSObject*> proto(cx);
-  if (!js::GetObjectProto(cx, parent, &proto))
-    return nullptr;
-
-  JSObject *obj = js::Wrapper::New(cx, parent, proto, parent,
+  JSObject *obj = js::Wrapper::New(cx, parent, parent,
                                    isChrome ? &nsChromeOuterWindowProxy::singleton
                                             : &nsOuterWindowProxy::singleton);
 
   NS_ASSERTION(js::GetObjectClass(obj)->ext.innerObject, "bad class");
   return obj;
 }
 
 //*****************************************************************************
@@ -2098,36 +2094,21 @@ nsGlobalWindow::CreateOuterObject(nsGlob
   JS::Rooted<JSObject*> global(cx, aNewInner->FastGetGlobalJSObject());
   JS::Rooted<JSObject*> outer(cx, NewOuterWindowProxy(cx, global, IsChromeWindow()));
   if (!outer) {
     return NS_ERROR_FAILURE;
   }
 
   js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this)));
 
-  return SetOuterObject(cx, outer);
-}
-
-nsresult
-nsGlobalWindow::SetOuterObject(JSContext* aCx, JS::Handle<JSObject*> aOuterObject)
-{
-  JSAutoCompartment ac(aCx, aOuterObject);
+  JSAutoCompartment ac(cx, outer);
 
   // Inform the nsJSContext, which is the canonical holder of the outer.
   MOZ_ASSERT(IsOuterWindow());
-  mContext->SetWindowProxy(aOuterObject);
-
-  // Set up the prototype for the outer object.
-  JS::Rooted<JSObject*> inner(aCx, JS_GetParent(aOuterObject));
-  JS::Rooted<JSObject*> proto(aCx);
-  if (!JS_GetPrototype(aCx, inner, &proto)) {
-    return NS_ERROR_FAILURE;
-  }
-  JS_SetPrototype(aCx, aOuterObject, proto);
-
+  mContext->SetWindowProxy(outer);
   return NS_OK;
 }
 
 // We need certain special behavior for remote XUL whitelisted domains, but we
 // don't want that behavior to take effect in automation, because we whitelist
 // all the mochitest domains. So we need to check a pref here.
 static bool
 TreatAsRemoteXUL(nsIPrincipal* aPrincipal)
@@ -2452,18 +2433,19 @@ nsGlobalWindow::SetNewDocument(nsIDocume
       SetWrapper(mJSObject);
 
       {
         JSAutoCompartment ac(cx, mJSObject);
 
         JS_SetParent(cx, mJSObject, newInnerWindow->mJSObject);
 
         JS::Rooted<JSObject*> obj(cx, mJSObject);
-        rv = SetOuterObject(cx, obj);
-        NS_ENSURE_SUCCESS(rv, rv);
+
+        // Inform the nsJSContext, which is the canonical holder of the outer.
+        mContext->SetWindowProxy(obj);
 
         NS_ASSERTION(!JS_IsExceptionPending(cx),
                      "We might overwrite a pending exception!");
         XPCWrappedNativeScope* scope = xpc::GetObjectScope(mJSObject);
         if (scope->mWaiverWrapperMap) {
           scope->mWaiverWrapperMap->Reparent(cx, newInnerWindow->mJSObject);
         }
       }
--- a/js/src/jsapi-tests/testBug604087.cpp
+++ b/js/src/jsapi-tests/testBug604087.cpp
@@ -55,23 +55,22 @@ PreWrap(JSContext *cx, JS::HandleObject 
     JS_GC(JS_GetRuntime(cx));
     return obj;
 }
 
 static JSObject *
 Wrap(JSContext *cx, JS::HandleObject existing, JS::HandleObject obj,
      JS::HandleObject proto, JS::HandleObject parent, unsigned flags)
 {
-    return js::Wrapper::New(cx, obj, proto, parent, &js::CrossCompartmentWrapper::singleton);
+    return js::Wrapper::New(cx, obj, parent, &js::CrossCompartmentWrapper::singleton);
 }
 
 BEGIN_TEST(testBug604087)
 {
-    JS::RootedObject outerObj(cx, js::Wrapper::New(cx, global, global->getProto(), global,
-                                               &OuterWrapper::singleton));
+    JS::RootedObject outerObj(cx, js::Wrapper::New(cx, global, global, &OuterWrapper::singleton));
     JS::RootedObject compartment2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
     JS::RootedObject compartment3(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
     JS::RootedObject compartment4(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook));
 
     JS::RootedObject c2wrapper(cx, wrap(cx, outerObj, compartment2));
     CHECK(c2wrapper);
     c2wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(2));
 
@@ -82,18 +81,17 @@ BEGIN_TEST(testBug604087)
     JS::RootedObject c4wrapper(cx, wrap(cx, outerObj, compartment4));
     CHECK(c4wrapper);
     c4wrapper->as<js::ProxyObject>().setExtra(0, js::Int32Value(4));
     compartment4 = c4wrapper = nullptr;
 
     JS::RootedObject next(cx);
     {
         JSAutoCompartment ac(cx, compartment2);
-        next = js::Wrapper::New(cx, compartment2, compartment2->getProto(), compartment2,
-                                &OuterWrapper::singleton);
+        next = js::Wrapper::New(cx, compartment2, compartment2, &OuterWrapper::singleton);
         CHECK(next);
     }
 
     JS_SetWrapObjectCallbacks(JS_GetRuntime(cx), Wrap, SameCompartmentWrap, PreWrap);
     CHECK(JS_TransplantObject(cx, outerObj, next));
     return true;
 }
 END_TEST(testBug604087)
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -511,16 +511,29 @@ DirectProxyHandler::hasInstance(JSContex
     bool b;
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     if (!JS_HasInstance(cx, target, v, &b))
         return false;
     *bp = !!b;
     return true;
 }
 
+bool
+DirectProxyHandler::getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop)
+{
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    return JSObject::getProto(cx, target, protop);
+}
+
+bool
+DirectProxyHandler::setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp)
+{
+    RootedObject target(cx, proxy->as<ProxyObject>().target());
+    return JSObject::setProto(cx, target, proto, bp);
+}
 
 bool
 DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
                                   JSContext *cx)
 {
     RootedObject target(cx, proxy->as<ProxyObject>().target());
     return ObjectClassIs(target, classValue, cx);
 }
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -247,16 +247,18 @@ class JS_PUBLIC_API(DirectProxyHandler) 
     /* Spidermonkey extensions. */
     virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
+    virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
+    virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto, bool *bp);
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
     virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy,
                                    unsigned indent) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
                                  RegExpGuard *g) MOZ_OVERRIDE;
     virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -34,26 +34,26 @@ Wrapper::defaultValue(JSContext *cx, Han
 {
     vp.set(ObjectValue(*proxy->as<ProxyObject>().target()));
     if (hint == JSTYPE_VOID)
         return ToPrimitive(cx, vp);
     return ToPrimitive(cx, hint, vp);
 }
 
 JSObject *
-Wrapper::New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, Wrapper *handler)
+Wrapper::New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler)
 {
     JS_ASSERT(parent);
 
     AutoMarkInDeadZone amd(cx->zone());
 
     RootedValue priv(cx, ObjectValue(*obj));
     ProxyOptions options;
     options.setCallable(obj->isCallable());
-    return NewProxyObject(cx, handler, priv, proto, parent, options);
+    return NewProxyObject(cx, handler, priv, Proxy::LazyProto, parent, options);
 }
 
 JSObject *
 Wrapper::Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler)
 {
     JS_ASSERT(!obj->isCallable());
     existing->as<ProxyObject>().renew(cx, handler, ObjectValue(*obj));
     return existing;
@@ -136,17 +136,18 @@ Wrapper Wrapper::singletonWithPrototype(
 
 extern JSObject *
 js::TransparentObjectWrapper(JSContext *cx, HandleObject existing, HandleObject obj,
                              HandleObject wrappedProto, HandleObject parent,
                              unsigned flags)
 {
     // Allow wrapping outer window proxies.
     JS_ASSERT(!obj->is<WrapperObject>() || obj->getClass()->ext.innerObject);
-    return Wrapper::New(cx, obj, wrappedProto, parent, &CrossCompartmentWrapper::singleton);
+    JS_ASSERT(wrappedProto == Proxy::LazyProto);
+    return Wrapper::New(cx, obj, parent, &CrossCompartmentWrapper::singleton);
 }
 
 ErrorCopier::~ErrorCopier()
 {
     JSContext *cx = ac.ref().context()->asJSContext();
     if (ac.ref().origin() != cx->compartment() && cx->isExceptionPending()) {
         RootedValue exc(cx, cx->getPendingException());
         if (exc.isObject() && exc.toObject().is<ErrorObject>()) {
@@ -573,33 +574,39 @@ CrossCompartmentWrapper::defaultValue(JS
            Wrapper::defaultValue(cx, wrapper, hint, vp),
            cx->compartment()->wrap(cx, vp));
 }
 
 bool
 CrossCompartmentWrapper::getPrototypeOf(JSContext *cx, HandleObject wrapper,
                                         MutableHandleObject protop)
 {
-    if (!wrapper->getTaggedProto().isLazy()) {
-        protop.set(wrapper->getTaggedProto().toObjectOrNull());
-        return true;
-    }
-
     {
         RootedObject wrapped(cx, wrappedObject(wrapper));
         AutoCompartment call(cx, wrapped);
         if (!JSObject::getProto(cx, wrapped, protop))
             return false;
         if (protop)
             protop->setDelegate(cx);
     }
 
     return cx->compartment()->wrap(cx, protop);
 }
 
+bool
+CrossCompartmentWrapper::setPrototypeOf(JSContext *cx, HandleObject wrapper,
+                                        HandleObject proto, bool *bp)
+{
+    RootedObject protoCopy(cx, proto);
+    PIERCE(cx, wrapper,
+           cx->compartment()->wrap(cx, &protoCopy),
+           Wrapper::setPrototypeOf(cx, wrapper, protoCopy, bp),
+           NOTHING);
+}
+
 CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
 
 /* Security wrappers. */
 
 template <class Base>
 SecurityWrapper<Base>::SecurityWrapper(unsigned flags)
   : Base(flags)
 {
@@ -641,16 +648,25 @@ template <class Base>
 bool
 SecurityWrapper<Base>::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                                   CallArgs args)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED);
     return false;
 }
 
+template <class Base>
+bool
+SecurityWrapper<Base>::setPrototypeOf(JSContext *cx, HandleObject wrapper,
+                                      HandleObject proto, bool *bp)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
+    return false;
+}
+
 // For security wrappers, we run the DefaultValue algorithm on the wrapper
 // itself, which means that the existing security policy on operations like
 // toString() will take effect and do the right thing here.
 template <class Base>
 bool
 SecurityWrapper<Base>::defaultValue(JSContext *cx, HandleObject wrapper,
                                     JSType hint, MutableHandleValue vp)
 {
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -44,18 +44,17 @@ class JS_FRIEND_API(Wrapper) : public Di
      * Wrappers can explicitly specify that they are unsafe to unwrap from a
      * security perspective (as is the case for SecurityWrappers). If a wrapper
      * is not safe to unwrap, operations requiring full access to the underlying
      * object (via CheckedUnwrap) will throw. Otherwise, they will succeed.
      */
     void setSafeToUnwrap(bool safe) { mSafeToUnwrap = safe; }
     bool isSafeToUnwrap() { return mSafeToUnwrap; }
 
-    static JSObject *New(JSContext *cx, JSObject *obj, JSObject *proto,
-                         JSObject *parent, Wrapper *handler);
+    static JSObject *New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler);
 
     static JSObject *Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler);
 
     static Wrapper *wrapperHandler(JSObject *wrapper);
 
     static JSObject *wrappedObject(JSObject *wrapper);
 
     unsigned flags() const {
@@ -115,17 +114,20 @@ class JS_FRIEND_API(CrossCompartmentWrap
     virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject wrapper,
                                    unsigned indent) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
-    virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
+    virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy,
+                                MutableHandleObject protop) MOZ_OVERRIDE;
+    virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
+                                bool *bp) MOZ_OVERRIDE;
 
     static CrossCompartmentWrapper singleton;
     static CrossCompartmentWrapper singletonWithPrototype;
 };
 
 /*
  * Base class for security wrappers. A security wrapper is potentially hiding
  * all or part of some wrapped object thus SecurityWrapper defaults to denying
@@ -150,16 +152,19 @@ class JS_FRIEND_API(SecurityWrapper) : p
     virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                 MutableHandle<JSPropertyDescriptor> desc) MOZ_OVERRIDE;
 
+    virtual bool setPrototypeOf(JSContext *cx, HandleObject proxy, HandleObject proto,
+                                bool *bp) MOZ_OVERRIDE;
+
     virtual bool watch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id,
                        JS::HandleObject callable) MOZ_OVERRIDE;
     virtual bool unwatch(JSContext *cx, JS::HandleObject proxy, JS::HandleId id) MOZ_OVERRIDE;
 
     /*
      * Allow our subclasses to select the superclass behavior they want without
      * needing to specify an exact superclass.
      */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3738,30 +3738,55 @@ ThisFilename(JSContext *cx, unsigned arg
     }
     JSString *filename = JS_NewStringCopyZ(cx, script->filename());
     if (!filename)
         return false;
     args.rval().setString(filename);
     return true;
 }
 
+/*
+ * Internal class for testing hasPrototype easily.
+ * Uses passed in prototype instead of target's.
+ */
+class WrapperWithProto : public Wrapper
+{
+  public:
+    explicit WrapperWithProto(unsigned flags)
+      : Wrapper(flags, true)
+    { }
+
+    static JSObject *New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
+                         Wrapper *handler);
+};
+
+/* static */ JSObject *
+WrapperWithProto::New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
+                      Wrapper *handler)
+{
+    JS_ASSERT(parent);
+    AutoMarkInDeadZone amd(cx->zone());
+
+    RootedValue priv(cx, ObjectValue(*obj));
+    ProxyOptions options;
+    options.setCallable(obj->isCallable());
+    return NewProxyObject(cx, handler, priv, proto, parent, options);
+}
+
 static bool
 Wrap(JSContext *cx, unsigned argc, jsval *vp)
 {
     jsval v = argc > 0 ? JS_ARGV(cx, vp)[0] : UndefinedValue();
     if (JSVAL_IS_PRIMITIVE(v)) {
         JS_SET_RVAL(cx, vp, v);
         return true;
     }
 
     RootedObject obj(cx, JSVAL_TO_OBJECT(v));
-    RootedObject proto(cx);
-    if (!JSObject::getProto(cx, obj, &proto))
-        return false;
-    JSObject *wrapped = Wrapper::New(cx, obj, proto, &obj->global(),
+    JSObject *wrapped = Wrapper::New(cx, obj, &obj->global(),
                                      &Wrapper::singleton);
     if (!wrapped)
         return false;
 
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(wrapped));
     return true;
 }
 
@@ -3774,19 +3799,19 @@ WrapWithProto(JSContext *cx, unsigned ar
         proto = JS_ARGV(cx, vp)[1];
     }
     if (!obj.isObject() || !proto.isObjectOrNull()) {
         JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS,
                              "wrapWithProto");
         return false;
     }
 
-    JSObject *wrapped = Wrapper::New(cx, &obj.toObject(), proto.toObjectOrNull(),
-                                     &obj.toObject().global(),
-                                     &Wrapper::singletonWithPrototype);
+    JSObject *wrapped = WrapperWithProto::New(cx, &obj.toObject(), proto.toObjectOrNull(),
+                                              &obj.toObject().global(),
+                                              &Wrapper::singletonWithPrototype);
     if (!wrapped)
         return false;
 
     JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(wrapped));
     return true;
 }
 
 static JSObject *
--- a/js/xpconnect/wrappers/WaiveXrayWrapper.cpp
+++ b/js/xpconnect/wrappers/WaiveXrayWrapper.cpp
@@ -86,9 +86,16 @@ WaiveXrayWrapper::construct(JSContext *c
 bool
 WaiveXrayWrapper::nativeCall(JSContext *cx, JS::IsAcceptableThis test,
                              JS::NativeImpl impl, JS::CallArgs args)
 {
     return CrossCompartmentWrapper::nativeCall(cx, test, impl, args) &&
            WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
 }
 
+bool
+WaiveXrayWrapper::getPrototypeOf(JSContext *cx, HandleObject wrapper, MutableHandleObject protop)
+{
+    return CrossCompartmentWrapper::getPrototypeOf(cx, wrapper, protop) &&
+           (!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop));
 }
+
+}
--- a/js/xpconnect/wrappers/WaiveXrayWrapper.h
+++ b/js/xpconnect/wrappers/WaiveXrayWrapper.h
@@ -33,14 +33,17 @@ class WaiveXrayWrapper : public js::Cros
     virtual bool call(JSContext *cx, JS::Handle<JSObject*> wrapper,
                       const JS::CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
                            const JS::CallArgs &args) MOZ_OVERRIDE;
 
     virtual bool nativeCall(JSContext *cx, JS::IsAcceptableThis test,
                             JS::NativeImpl impl, JS::CallArgs args) MOZ_OVERRIDE;
 
+    virtual bool getPrototypeOf(JSContext *cx, JS::Handle<JSObject*> wrapper,
+                                JS::MutableHandle<JSObject*> protop) MOZ_OVERRIDE;
+
     static WaiveXrayWrapper singleton;
 };
 
 }
 
 #endif
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -70,28 +70,18 @@ WrapperFactory::GetXrayWaiver(HandleObje
 JSObject *
 WrapperFactory::CreateXrayWaiver(JSContext *cx, HandleObject obj)
 {
     // The caller is required to have already done a lookup.
     // NB: This implictly performs the assertions of GetXrayWaiver.
     MOZ_ASSERT(!GetXrayWaiver(obj));
     XPCWrappedNativeScope *scope = GetObjectScope(obj);
 
-    // Get a waiver for the proto.
-    RootedObject proto(cx);
-    if (!js::GetObjectProto(cx, obj, &proto))
-        return nullptr;
-    if (proto && !(proto = WaiveXray(cx, proto)))
-        return nullptr;
-
-    // Create the waiver.
     JSAutoCompartment ac(cx, obj);
-    if (!JS_WrapObject(cx, &proto))
-        return nullptr;
-    JSObject *waiver = Wrapper::New(cx, obj, proto,
+    JSObject *waiver = Wrapper::New(cx, obj,
                                     JS_GetGlobalForObject(cx, obj),
                                     &XrayWaiver);
     if (!waiver)
         return nullptr;
 
     // Add the new waiver to the map. It's important that we only ever have
     // one waiver for the lifetime of the target object.
     if (!scope->mWaiverWrapperMap) {
@@ -404,20 +394,16 @@ WrapperFactory::Rewrap(JSContext *cx, Ha
     bool originIsChrome = AccessCheck::isChrome(origin);
     bool targetIsChrome = AccessCheck::isChrome(target);
     bool originSubsumesTarget = AccessCheck::subsumes(origin, target);
     bool targetSubsumesOrigin = AccessCheck::subsumes(target, origin);
     bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget;
     XrayType xrayType = GetXrayType(obj);
     bool waiveXrayFlag = flags & WAIVE_XRAY_WRAPPER_FLAG;
 
-    // By default we use the wrapped proto of the underlying object as the
-    // prototype for our wrapper, but we may select something different below.
-    RootedObject proxyProto(cx, wrappedProto);
-
     Wrapper *wrapper;
     CompartmentPrivate *targetdata = EnsureCompartmentPrivate(target);
 
     //
     // First, handle the special cases.
     //
 
     // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use
@@ -493,20 +479,20 @@ WrapperFactory::Rewrap(JSContext *cx, Ha
                 JS_ReportError(cx, "Not allowed to access chrome eval or Function from content");
                 return nullptr;
             }
         }
     }
 
     DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target);
 
-    if (existing && proxyProto == wrappedProto)
+    if (existing)
         return Wrapper::Renew(cx, existing, obj, wrapper);
 
-    return Wrapper::New(cx, obj, proxyProto, parent, wrapper);
+    return Wrapper::New(cx, obj, parent, wrapper);
 }
 
 JSObject *
 WrapperFactory::WrapForSameCompartment(JSContext *cx, HandleObject objArg)
 {
     RootedObject obj(cx, objArg);
     MOZ_ASSERT(js::IsObjectInContextCompartment(obj, cx));
 
@@ -544,20 +530,32 @@ WrapperFactory::WrapForSameCompartment(J
 // using the returned object. If the object to be wrapped is already in the
 // correct compartment, then this returns the unwrapped object.
 bool
 WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleValue vp)
 {
     if (vp.isPrimitive())
         return JS_WrapValue(cx, vp);
 
-    JSObject *obj = js::UncheckedUnwrap(&vp.toObject());
+    RootedObject obj(cx, &vp.toObject());
+    if (!WaiveXrayAndWrap(cx, &obj))
+        return false;
+
+    vp.setObject(*obj);
+    return true;
+}
+
+bool
+WrapperFactory::WaiveXrayAndWrap(JSContext *cx, MutableHandleObject argObj)
+{
+    MOZ_ASSERT(argObj);
+    RootedObject obj(cx, js::UncheckedUnwrap(argObj));
     MOZ_ASSERT(!js::IsInnerObject(obj));
     if (js::IsObjectInContextCompartment(obj, cx)) {
-        vp.setObject(*obj);
+        argObj.set(obj);
         return true;
     }
 
     // Even though waivers have no effect on access by scopes that don't subsume
     // the underlying object, good defense-in-depth dictates that we should avoid
     // handing out waivers to callers that can't use them. The transitive waiving
     // machinery unconditionally calls WaiveXrayAndWrap on return values from
     // waived functions, even though the return value might be not be same-origin
@@ -565,54 +563,50 @@ WrapperFactory::WaiveXrayAndWrap(JSConte
     // |cx|, we should check whether the caller has any business with waivers
     // to things in |obj|'s compartment.
     JSCompartment *target = js::GetContextCompartment(cx);
     JSCompartment *origin = js::GetObjectCompartment(obj);
     obj = AccessCheck::subsumes(target, origin) ? WaiveXray(cx, obj) : obj;
     if (!obj)
         return false;
 
-    vp.setObject(*obj);
-    return JS_WrapValue(cx, vp);
+    if (!JS_WrapObject(cx, &obj))
+        return false;
+    argObj.set(obj);
+    return true;
 }
 
 JSObject *
 WrapperFactory::WrapSOWObject(JSContext *cx, JSObject *objArg)
 {
     RootedObject obj(cx, objArg);
-    RootedObject proto(cx);
 
     // If we're not allowing XBL scopes, that means we're running as a remote
     // XUL domain, in which we can't have SOWs. We should never be called in
     // that case.
     MOZ_ASSERT(xpc::AllowXBLScope(js::GetContextCompartment(cx)));
-    if (!JS_GetPrototype(cx, obj, &proto))
-        return nullptr;
     JSObject *wrapperObj =
-        Wrapper::New(cx, obj, proto, JS_GetGlobalForObject(cx, obj),
+        Wrapper::New(cx, obj, JS_GetGlobalForObject(cx, obj),
                      &FilteringWrapper<SameCompartmentSecurityWrapper,
                      Opaque>::singleton);
     return wrapperObj;
 }
 
 bool
 WrapperFactory::IsComponentsObject(JSObject *obj)
 {
     const char *name = js::GetObjectClass(obj)->name;
     return name[0] == 'n' && !strcmp(name, "nsXPCComponents");
 }
 
 JSObject *
 WrapperFactory::WrapComponentsObject(JSContext *cx, HandleObject obj)
 {
-    RootedObject proto(cx);
-    if (!JS_GetPrototype(cx, obj, &proto))
-        return nullptr;
     JSObject *wrapperObj =
-        Wrapper::New(cx, obj, proto, JS_GetGlobalForObject(cx, obj),
+        Wrapper::New(cx, obj, JS_GetGlobalForObject(cx, obj),
                      &FilteringWrapper<SameCompartmentSecurityWrapper, ComponentsObjectPolicy>::singleton);
 
     return wrapperObj;
 }
 
 bool
 WrapperFactory::XrayWrapperNotShadowing(JSObject *wrapper, jsid id)
 {
--- a/js/xpconnect/wrappers/WrapperFactory.h
+++ b/js/xpconnect/wrappers/WrapperFactory.h
@@ -59,16 +59,17 @@ class WrapperFactory {
                             unsigned flags);
 
     // Wrap an object for same-compartment access.
     static JSObject *WrapForSameCompartment(JSContext *cx,
                                             JS::HandleObject obj);
 
     // Wrap wrapped object into a waiver wrapper and then re-wrap it.
     static bool WaiveXrayAndWrap(JSContext *cx, JS::MutableHandleValue vp);
+    static bool WaiveXrayAndWrap(JSContext *cx, JS::MutableHandleObject object);
 
     // Wrap a (same compartment) object in a SOW.
     static JSObject *WrapSOWObject(JSContext *cx, JSObject *obj);
 
     // Return true if this is a Components object.
     static bool IsComponentsObject(JSObject *obj);
 
     // Wrap a (same compartment) Components object.
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -299,16 +299,17 @@ GetXrayTraits(JSObject *obj)
  * not imply that code in the target compartment should be allowed to inspect
  * them. They are private to the origin that placed them.
  */
 
 enum ExpandoSlots {
     JSSLOT_EXPANDO_NEXT = 0,
     JSSLOT_EXPANDO_ORIGIN,
     JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL,
+    JSSLOT_EXPANDO_PROTOTYPE,
     JSSLOT_EXPANDO_COUNT
 };
 
 static nsIPrincipal*
 ObjectPrincipal(JSObject *obj)
 {
     return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
 }
@@ -1787,16 +1788,66 @@ XrayWrapper<Base, Traits>::defaultValue(
     // Even if this isn't a security wrapper, Xray semantics dictate that we
     // run the DefaultValue algorithm directly on the Xray wrapper.
     //
     // NB: We don't have to worry about things with special [[DefaultValue]]
     // behavior like Date because we'll never have an XrayWrapper to them.
     return js::DefaultValue(cx, wrapper, hint, vp);
 }
 
+template <typename Base, typename Traits>
+bool
+XrayWrapper<Base, Traits>::getPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
+                                          JS::MutableHandleObject protop)
+{
+    RootedObject target(cx, Traits::getTargetObject(wrapper));
+    RootedObject expando(cx, Traits::singleton.getExpandoObject(cx, target, wrapper));
+
+    // We want to keep the Xray's prototype distinct from that of content, but only
+    // if there's been a set. If there's not an expando, or the expando slot is |undefined|,
+    // hand back content's proto, appropriately wrapped.
+    //
+    // NB: Our baseclass's getPrototypeOf() will appropriately wrap its return value, so there is
+    // no need for us to.
+
+    if (!expando)
+        return Base::getPrototypeOf(cx, wrapper, protop);
+
+    RootedValue v(cx);
+    {
+        JSAutoCompartment ac(cx, expando);
+        v = JS_GetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE);
+    }
+
+    if (v.isUndefined())
+        return Base::getPrototypeOf(cx, wrapper, protop);
+
+    protop.set(v.toObjectOrNull());
+    return JS_WrapObject(cx, protop);
+}
+
+template <typename Base, typename Traits>
+bool
+XrayWrapper<Base, Traits>::setPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
+                                          JS::HandleObject proto, bool *bp)
+{
+    RootedObject target(cx, Traits::getTargetObject(wrapper));
+    RootedObject expando(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, target));
+
+    // The expando lives in the target's compartment, so do our installation there.
+    JSAutoCompartment ac(cx, target);
+
+    RootedValue v(cx, ObjectValue(*proto));
+    if (!JS_WrapValue(cx, &v))
+        return false;
+    JS_SetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE, v);
+    *bp = true;
+    return true;
+}
+
 
 /*
  * The Permissive / Security variants should be used depending on whether the
  * compartment of the wrapper is guranteed to subsume the compartment of the
  * wrapped object (i.e. - whether it is safe from a security perspective to
  * unwrap the wrapper).
  */
 
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -101,16 +101,21 @@ class XrayWrapper : public Base {
                       const JS::CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, JS::Handle<JSObject*> wrapper,
                            const JS::CallArgs &args) MOZ_OVERRIDE;
 
     virtual bool defaultValue(JSContext *cx, JS::HandleObject wrapper,
                               JSType hint, JS::MutableHandleValue vp)
                               MOZ_OVERRIDE;
 
+    virtual bool getPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
+                                JS::MutableHandleObject protop) MOZ_OVERRIDE;
+    virtual bool setPrototypeOf(JSContext *cx, JS::HandleObject wrapper,
+                                JS::HandleObject proto, bool *bp) MOZ_OVERRIDE;
+
     static XrayWrapper singleton;
 
   private:
     bool enumerate(JSContext *cx, JS::Handle<JSObject*> wrapper, unsigned flags,
                    JS::AutoIdVector &props);
 };
 
 #define PermissiveXrayXPCWN xpc::XrayWrapper<js::CrossCompartmentWrapper, xpc::XPCWrappedNativeXrayTraits>