Bug 789897 - Implement isExtensible and preventExtensions traps for proxies, including scriptable direct proxies, and in the various wrapper classes. Also move non-static JSObject::preventExtensions to static ObjectImpl::preventExtensions, for handle correctness. Patch is a semi-tag-team effort with jwalden. r=jwalden, r=ejpbruel, r=bholley, r=two-turtle-doves-and-a-partridge-in-a-pear-tree
authorEddy Bruel <ejpbruel@mozilla.com>
Fri, 22 Mar 2013 19:43:03 -0700
changeset 137954 f5b92b22981cf83ae7996666b8fa8b18bec89a31
parent 137953 8ea262c8bb9ce6cd773eb1ff84dee339b33659d8
child 137955 b2cb3d603fa6ccfa57474959c022940258c51e38
push id336
push userakeybl@mozilla.com
push dateMon, 17 Jun 2013 22:53:19 +0000
treeherdermozilla-release@574a39cdf657 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden, ejpbruel, bholley, two-turtle-doves-and-a-partridge-in-a-pear-tree
bugs789897
milestone22.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 789897 - Implement isExtensible and preventExtensions traps for proxies, including scriptable direct proxies, and in the various wrapper classes. Also move non-static JSObject::preventExtensions to static ObjectImpl::preventExtensions, for handle correctness. Patch is a semi-tag-team effort with jwalden. r=jwalden, r=ejpbruel, r=bholley, r=two-turtle-doves-and-a-partridge-in-a-pear-tree
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
js/src/builtin/Object.cpp
js/src/jit-test/tests/proxy/testDirectProxyDefineProperty2.js
js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions1.js
js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions2.js
js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions3.js
js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions4.js
js/src/js.msg
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/tests/ecma_5/extensions/preventExtensions-cross-global.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger.cpp
js/src/vm/GlobalObject.cpp
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.h
js/src/vm/ScopeObject.cpp
js/src/vm/Shape.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
js/xpconnect/wrappers/XrayWrapper.h
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -71,16 +71,30 @@ DOMProxyHandler::EnsureExpandoObject(JSC
     cache->SetPreservingWrapper(true);
 
     js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
   }
   return expando;
 }
 
 bool
+DOMProxyHandler::isExtensible(JSObject *proxy)
+{
+  return true; // always extensible per WebIDL
+}
+
+bool
+DOMProxyHandler::preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy)
+{
+  // Throw a TypeError, per WebIDL.
+  JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+  return false;
+}
+
+bool
 DOMProxyHandler::getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                                        JSPropertyDescriptor* desc, unsigned flags)
 {
   if (!getOwnPropertyDescriptor(cx, proxy, id, desc, flags)) {
     return false;
   }
   if (desc->obj) {
     return true;
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -31,24 +31,26 @@ class DOMProxyHandler : public js::BaseP
 {
 public:
   DOMProxyHandler(const DOMClass& aClass)
     : js::BaseProxyHandler(ProxyFamily()),
       mClass(aClass)
   {
   }
 
+  bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> proxy) MOZ_OVERRIDE;
   bool getPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                             JSPropertyDescriptor* desc, unsigned flags) MOZ_OVERRIDE;
   bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                       JSPropertyDescriptor* desc) MOZ_OVERRIDE;
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
+  bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
   using js::BaseProxyHandler::obj_toString;
 
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
     return v.isUndefined() ? NULL : v.toObjectOrNull();
   }
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -909,17 +909,17 @@ obj_preventExtensions(JSContext *cx, uns
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, argc, vp, "Object.preventExtensions", &obj))
         return false;
 
     vp->setObject(*obj);
     if (!obj->isExtensible())
         return true;
 
-    return obj->preventExtensions(cx);
+    return JSObject::preventExtensions(cx, obj);
 }
 
 static JSBool
 obj_freeze(JSContext *cx, unsigned argc, Value *vp)
 {
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, argc, vp, "Object.freeze", &obj))
         return false;
--- a/js/src/jit-test/tests/proxy/testDirectProxyDefineProperty2.js
+++ b/js/src/jit-test/tests/proxy/testDirectProxyDefineProperty2.js
@@ -19,10 +19,17 @@ var handler = {
     }
 }
 var desc = {
     value: 'bar',
     writable: true,
     enumerable: false,
     configurable: true
 };
-Object.defineProperty(new Proxy(target, handler), 'foo', desc);
+
+var p = new Proxy(target, handler);
+Object.defineProperty(p, 'foo', desc);
 assertEq(called, true);
+assertEq(Object.isExtensible(target), true);
+assertEq(Object.isExtensible(p), true);
+Object.preventExtensions(target);
+assertEq(Object.isExtensible(target), false);
+assertEq(Object.isExtensible(p), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions1.js
@@ -0,0 +1,4 @@
+// Forward to the target if the trap is not defined
+var target = {};
+Object.preventExtensions(new Proxy(target, {}));
+assertEq(Object.isExtensible(target), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions2.js
@@ -0,0 +1,17 @@
+/*
+ * Call the trap with the handler as the this value and the target as the first
+ * argument.
+ */
+var target = {};
+var called = false;
+var handler = {
+    preventExtensions: function (target1) {
+        assertEq(this, handler);
+        assertEq(target1, target);
+        Object.preventExtensions(target1);
+        called = true;
+        return true;
+    }
+};
+Object.preventExtensions(new Proxy(target, handler));
+assertEq(called, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions3.js
@@ -0,0 +1,10 @@
+load(libdir + "asserts.js");
+
+// Throw a TypeError if the trap reports an extensible object as non-extensible
+assertThrowsInstanceOf(function () {
+    Object.preventExtensions(new Proxy({}, {
+        preventExtensions: function () {
+            return true;
+        }
+    }));
+}, TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/proxy/testDirectProxyPreventExtensions4.js
@@ -0,0 +1,10 @@
+load(libdir + "asserts.js");
+
+// Throw a TypeError if the object refuses to be made non-extensible
+assertThrowsInstanceOf(function () {
+    Object.preventExtensions(new Proxy({}, {
+        preventExtensions: function () {
+            return false;
+        }
+    }));
+}, TypeError);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -230,17 +230,17 @@ MSG_DEF(JSMSG_UNUSED176,              17
 MSG_DEF(JSMSG_UNUSED177,              177, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED178,              178, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED179,              179, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED180,              180, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED181,              181, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_BAD_GENERATOR_SEND,     182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator")
 MSG_DEF(JSMSG_UNUSED183,              183, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED184,              184, 0, JSEXN_NONE, "")
-MSG_DEF(JSMSG_UNUSED185,              185, 0, JSEXN_NONE, "")
+MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 185, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible")
 MSG_DEF(JSMSG_UNUSED186,              186, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED187,              187, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_INCOMPATIBLE_METHOD,    188, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
 MSG_DEF(JSMSG_UNUSED189,              189, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED190,              190, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED191,              191, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_UNUSED192,              192, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_BAD_FOR_EACH_LOOP,      193, 0, JSEXN_SYNTAXERR, "invalid for each loop")
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1067,17 +1067,17 @@ JSObject::getSealedOrFrozenAttributes(un
 }
 
 /* static */ bool
 JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it)
 {
     assertSameCompartment(cx, obj);
     JS_ASSERT(it == SEAL || it == FREEZE);
 
-    if (obj->isExtensible() && !obj->preventExtensions(cx))
+    if (obj->isExtensible() && !JSObject::preventExtensions(cx, obj))
         return false;
 
     AutoIdVector props(cx);
     if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
         return false;
 
     /* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */
     JS_ASSERT_IF(obj->isNative(), obj->getDenseCapacity() == 0);
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -543,18 +543,16 @@ class JSObject : public js::ObjectImpl
      */
     static bool sealOrFreeze(JSContext *cx, js::HandleObject obj, ImmutabilityType it);
 
     static bool isSealedOrFrozen(JSContext *cx, js::HandleObject obj, ImmutabilityType it, bool *resultp);
 
     static inline unsigned getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it);
 
   public:
-    bool preventExtensions(JSContext *cx);
-
     /* ES5 15.2.3.8: non-extensible, all props non-configurable */
     static inline bool seal(JSContext *cx, js::HandleObject obj) { return sealOrFreeze(cx, obj, SEAL); }
     /* ES5 15.2.3.9: non-extensible, all properties non-configurable, all data props read-only */
     static inline bool freeze(JSContext *cx, js::HandleObject obj) { return sealOrFreeze(cx, obj, FREEZE); }
 
     static inline bool isSealed(JSContext *cx, js::HandleObject obj, bool *resultp) {
         return isSealedOrFrozen(cx, obj, SEAL, resultp);
     }
@@ -955,17 +953,17 @@ class JSObject : public js::ObjectImpl
     inline bool isFunction() const;
     inline bool isGenerator() const;
     inline bool isGlobal() const;
     inline bool isMapIterator() const;
     inline bool isModule() const;
     inline bool isObject() const;
     inline bool isPrimitive() const;
     inline bool isPropertyIterator() const;
-    inline bool isProxy() const;
+    using js::ObjectImpl::isProxy;
     inline bool isRegExp() const;
     inline bool isRegExpStatics() const;
     inline bool isScope() const;
     inline bool isScript() const;
     inline bool isSetIterator() const;
     inline bool isStopIteration() const;
     inline bool isTypedArray() const;
     inline bool isWeakMap() const;
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -1253,19 +1253,19 @@ JSObject::getElementAttributes(JSContext
 JSObject::getSpecialAttributes(JSContext *cx, js::HandleObject obj,
                                js::SpecialId sid, unsigned *attrsp)
 {
     JS::RootedId id(cx, SPECIALID_TO_JSID(sid));
     return getGenericAttributes(cx, obj, id, attrsp);
 }
 
 inline bool
-JSObject::isProxy() const
+js::ObjectImpl::isProxy() const
 {
-    return js::IsProxy(const_cast<JSObject*>(this));
+    return js::IsProxy(const_cast<JSObject*>(this->asObjectPtr()));
 }
 
 inline bool
 JSObject::isCrossCompartmentWrapper() const
 {
     return js::IsCrossCompartmentWrapper(const_cast<JSObject*>(this));
 }
 
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -634,16 +634,29 @@ DirectProxyHandler::iterate(JSContext *c
                             MutableHandleValue vp)
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     JS_ASSERT(!hasPrototype()); // Should never be called if there's a prototype.
     RootedObject target(cx, GetProxyTargetObject(proxy));
     return GetIterator(cx, target, flags, vp);
 }
 
+bool
+DirectProxyHandler::isExtensible(JSObject *proxy)
+{
+    return GetProxyTargetObject(proxy)->isExtensible();
+}
+
+bool
+DirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    RootedObject target(cx, GetProxyTargetObject(proxy));
+    return JSObject::preventExtensions(cx, target);
+}
+
 static bool
 GetFundamentalTrap(JSContext *cx, HandleObject handler, HandlePropertyName name,
                    MutableHandleValue fvalp)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     return JSObject::getProperty(cx, handler, handler, name, fvalp);
 }
@@ -752,22 +765,24 @@ ArrayToIdVector(JSContext *cx, const Val
         if (!props.append(id))
             return false;
     }
 
     return true;
 }
 
 /* Derived class for all scripted indirect proxy handlers. */
-class ScriptedIndirectProxyHandler : public BaseProxyHandler {
+class ScriptedIndirectProxyHandler : public BaseProxyHandler
+{
   public:
     ScriptedIndirectProxyHandler();
     virtual ~ScriptedIndirectProxyHandler();
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                           PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
@@ -780,16 +795,17 @@ class ScriptedIndirectProxyHandler : pub
                      MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                      bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
+    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, unsigned argc,
                            Value *argv, MutableHandleValue rval) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
@@ -803,16 +819,31 @@ ScriptedIndirectProxyHandler::ScriptedIn
         : BaseProxyHandler(&sScriptedIndirectProxyHandlerFamily)
 {
 }
 
 ScriptedIndirectProxyHandler::~ScriptedIndirectProxyHandler()
 {
 }
 
+bool
+ScriptedIndirectProxyHandler::isExtensible(JSObject *proxy)
+{
+    // Scripted indirect proxies don't support extensibility changes.
+    return true;
+}
+
+bool
+ScriptedIndirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+    return false;
+}
+
 static bool
 ReturnedValueMustNotBePrimitive(JSContext *cx, HandleObject proxy, JSAtom *atom, const Value &v)
 {
     if (v.isPrimitive()) {
         JSAutoByteString bytes;
         if (js_AtomToPrintableString(cx, atom, &bytes)) {
             RootedValue val(cx, ObjectOrNullValue(proxy));
             js_ReportValueError2(cx, JSMSG_BAD_TRAP_RETURN_VALUE,
@@ -1057,16 +1088,17 @@ GetDirectProxyHandlerObject(JSObject *pr
 
 /* Derived class for all scripted direct proxy handlers. */
 class ScriptedDirectProxyHandler : public DirectProxyHandler {
   public:
     ScriptedDirectProxyHandler();
     virtual ~ScriptedDirectProxyHandler();
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                           PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
@@ -1625,16 +1657,58 @@ ScriptedDirectProxyHandler::ScriptedDire
         : DirectProxyHandler(&sScriptedDirectProxyHandlerFamily)
 {
 }
 
 ScriptedDirectProxyHandler::~ScriptedDirectProxyHandler()
 {
 }
 
+bool
+ScriptedDirectProxyHandler::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    // step a
+    RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
+
+    // step b
+    RootedObject target(cx, GetProxyTargetObject(proxy));
+
+    // step c
+    RootedValue trap(cx);
+    if (!JSObject::getProperty(cx, handler, handler, cx->names().preventExtensions, &trap))
+        return false;
+
+    // step d
+    if (trap.isUndefined())
+        return DirectProxyHandler::preventExtensions(cx, proxy);
+
+    // step e
+    Value argv[] = {
+        ObjectValue(*target)
+    };
+    RootedValue trapResult(cx);
+    if (!Invoke(cx, ObjectValue(*handler), trap, 1, argv, trapResult.address()))
+        return false;
+
+    // step f
+    bool success = ToBoolean(trapResult);
+    if (success) {
+        // step g
+        if (target->isExtensible()) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE);
+            return false;
+        }
+        return true;
+    }
+
+    // step h
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+    return false;
+}
+
 // FIXME: Move to Proxy::getPropertyDescriptor once ScriptedIndirectProxy is removed
 bool
 ScriptedDirectProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                                   PropertyDescriptor *desc, unsigned flags)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     if (!GetOwnPropertyDescriptor(cx, proxy, id, desc))
@@ -2518,16 +2592,30 @@ Proxy::iterate(JSContext *cx, HandleObje
         ? !Proxy::keys(cx, proxy, props)
         : !Proxy::enumerate(cx, proxy, props)) {
         return false;
     }
     return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
 }
 
 bool
+Proxy::isExtensible(JSObject *proxy)
+{
+    return GetProxyHandler(proxy)->isExtensible(proxy);
+}
+
+bool
+Proxy::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    return handler->preventExtensions(cx, proxy);
+}
+
+bool
 Proxy::call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
 
     // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
     // can only set our default value once we're sure that we're not calling the
     // trap.
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -43,17 +43,18 @@ class JS_FRIEND_API(Wrapper);
 
 /*
  * BaseProxyHandler is the most generic kind of proxy handler. It does not make
  * any assumptions about the target. Consequently, it does not provide any
  * default implementation for the fundamental traps. It does, however, implement
  * the derived traps in terms of the fundamental ones. This allows consumers of
  * this class to define any custom behavior they want.
  */
-class JS_FRIEND_API(BaseProxyHandler) {
+class JS_FRIEND_API(BaseProxyHandler)
+{
     void *mFamily;
     bool mHasPrototype;
     bool mHasPolicy;
   protected:
     // Subclasses may set this in their constructor.
     void setHasPrototype(bool aHasPrototype) { mHasPrototype = aHasPrototype; }
     void setHasPolicy(bool aHasPolicy) { mHasPolicy = aHasPolicy; }
 
@@ -99,16 +100,17 @@ class JS_FRIEND_API(BaseProxyHandler) {
         GET,
         SET,
         CALL
     };
     virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Action act,
                        bool *bp);
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) = 0;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) = 0;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy,
                                           HandleId id, PropertyDescriptor *desc,
                                           unsigned flags) = 0;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) = 0;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
@@ -123,16 +125,17 @@ class JS_FRIEND_API(BaseProxyHandler) {
                      HandleId id, MutableHandleValue vp);
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp);
     virtual bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp);
 
     /* Spidermonkey extensions. */
+    virtual bool isExtensible(JSObject *proxy) = 0;
     virtual bool call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp);
     virtual bool construct(JSContext *cx, HandleObject proxy, unsigned argc,
                            Value *argv, MutableHandleValue rval);
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     virtual JSString *obj_toString(JSContext *cx, HandleObject proxy);
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
@@ -148,21 +151,23 @@ class JS_FRIEND_API(BaseProxyHandler) {
 };
 
 /*
  * DirectProxyHandler includes a notion of a target object. All traps are
  * reimplemented such that they forward their behavior to the target. This
  * allows consumers of this class to forward to another object as transparently
  * and efficiently as possible.
  */
-class JS_PUBLIC_API(DirectProxyHandler) : public BaseProxyHandler {
-public:
+class JS_PUBLIC_API(DirectProxyHandler) : public BaseProxyHandler
+{
+  public:
     explicit DirectProxyHandler(void *family);
 
     /* ES5 Harmony fundamental proxy traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy,
                                           HandleId id, PropertyDescriptor *desc,
                                           unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy,
@@ -182,16 +187,17 @@ public:
     virtual bool set(JSContext *cx, HandleObject proxy, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject proxy,
                       AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
+    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, unsigned argc,
                            Value *argv, MutableHandleValue rval) 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 objectClassIs(HandleObject obj, ESClassValue classValue,
@@ -202,19 +208,21 @@ public:
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
                                  RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
 };
 
 /* Dispatch point for handlers that executes the appropriate C++ or scripted traps. */
-class Proxy {
+class Proxy
+{
   public:
     /* ES5 Harmony fundamental proxy traps. */
+    static bool preventExtensions(JSContext *cx, HandleObject proxy);
     static bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                       PropertyDescriptor *desc, unsigned flags);
     static bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, unsigned flags, HandleId id,
                                       MutableHandleValue vp);
     static bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                          PropertyDescriptor *desc, unsigned flags);
     static bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, unsigned flags, HandleId id,
                                          MutableHandleValue vp);
@@ -232,16 +240,17 @@ class Proxy {
     static bool getElementIfPresent(JSContext *cx, HandleObject proxy, HandleObject receiver,
                                     uint32_t index, MutableHandleValue vp, bool *present);
     static bool set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
                     bool strict, MutableHandleValue vp);
     static bool keys(JSContext *cx, HandleObject proxy, AutoIdVector &props);
     static bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp);
 
     /* Spidermonkey extensions. */
+    static bool isExtensible(JSObject *proxy);
     static bool call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp);
     static bool construct(JSContext *cx, HandleObject proxy, unsigned argc, Value *argv,
                           MutableHandleValue rval);
     static bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     static bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     static bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     static JSString *obj_toString(JSContext *cx, HandleObject proxy);
     static JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -222,16 +222,40 @@ bool CrossCompartmentWrapper::finalizeIn
             ok = (pre) && (op);                                 \
         }                                                       \
         return ok && (post);                                    \
     JS_END_MACRO
 
 #define NOTHING (true)
 
 bool
+CrossCompartmentWrapper::isExtensible(JSObject *wrapper)
+{
+    // The lack of a context to enter a compartment here is troublesome.  We
+    // don't know anything about the wrapped object (it might even be a
+    // proxy!), and embeddings' proxy handlers could theoretically trigger
+    // compartment mismatches here (because isExtensible wouldn't be called in
+    // the wrapped object's compartment.  But that's probably not very likely.
+    // (Famous last words.)
+    //
+    // Given that we're likely going to make this method take a context and
+    // maybe be fallible at some point, punt on the issue for now.
+    return wrappedObject(wrapper)->isExtensible();
+}
+
+bool
+CrossCompartmentWrapper::preventExtensions(JSContext *cx, HandleObject wrapper)
+{
+    PIERCE(cx, wrapper,
+           NOTHING,
+           Wrapper::preventExtensions(cx, wrapper),
+           NOTHING);
+}
+
+bool
 CrossCompartmentWrapper::getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                                PropertyDescriptor *desc, unsigned flags)
 {
     RootedId idCopy(cx, id);
     PIERCE(cx, wrapper,
            cx->compartment->wrapId(cx, idCopy.address()),
            Wrapper::getPropertyDescriptor(cx, wrapper, idCopy, desc, flags),
            cx->compartment->wrap(cx, desc));
@@ -623,26 +647,45 @@ SecurityWrapper<Base>::SecurityWrapper(u
   : Base(flags)
 {
     Base::setSafeToUnwrap(false);
     BaseProxyHandler::setHasPolicy(true);
 }
 
 template <class Base>
 bool
+SecurityWrapper<Base>::isExtensible(JSObject *wrapper)
+{
+    // Just like BaseProxyHandler, SecurityWrappers claim by default to always
+    // be extensible, so as not to leak information about the state of the
+    // underlying wrapped thing.
+    return true;
+}
+
+template <class Base>
+bool
+SecurityWrapper<Base>::preventExtensions(JSContext *cx, HandleObject wrapper)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
+    return false;
+}
+
+template <class Base>
+bool
 SecurityWrapper<Base>::enter(JSContext *cx, HandleObject wrapper, HandleId id,
                              Wrapper::Action act, bool *bp)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
     *bp = false;
     return false;
 }
 
- template <class Base>
- bool
+template <class Base>
+bool
 SecurityWrapper<Base>::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                                   CallArgs args)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
     return false;
 }
 
 template <class Base>
@@ -679,16 +722,31 @@ template class js::SecurityWrapper<Wrapp
 template class js::SecurityWrapper<CrossCompartmentWrapper>;
 
 DeadObjectProxy::DeadObjectProxy()
   : BaseProxyHandler(&sDeadObjectFamily)
 {
 }
 
 bool
+DeadObjectProxy::isExtensible(JSObject *proxy)
+{
+    // This is kind of meaningless, but dead-object semantics aside,
+    // [[Extensible]] always being true is consistent with other proxy types.
+    return true;
+}
+
+bool
+DeadObjectProxy::preventExtensions(JSContext *cx, HandleObject proxy)
+{
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEAD_OBJECT);
+    return false;
+}
+
+bool
 DeadObjectProxy::getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEAD_OBJECT);
     return false;
 }
 
 bool
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -81,16 +81,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
   public:
     CrossCompartmentWrapper(unsigned flags, bool hasPrototype = false);
 
     virtual ~CrossCompartmentWrapper();
 
     virtual bool finalizeInBackground(Value priv) MOZ_OVERRIDE;
 
     /* ES5 Harmony fundamental wrapper traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                           PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject wrapper,
                                      AutoIdVector &props) MOZ_OVERRIDE;
@@ -104,16 +105,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
                      HandleId id, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool set(JSContext *cx, HandleObject wrapper, HandleObject receiver,
                      HandleId id, bool strict, MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool keys(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject wrapper, unsigned flags,
                          MutableHandleValue vp) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
+    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject wrapper, unsigned argc, Value *vp) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject wrapper, unsigned argc, Value *argv,
                            MutableHandleValue rval) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual JSString *obj_toString(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
@@ -138,16 +140,18 @@ class JS_FRIEND_API(CrossCompartmentWrap
  * access, relying on derived SecurityWrapper to block access when necessary.
  */
 template <class Base>
 class JS_FRIEND_API(SecurityWrapper) : public Base
 {
   public:
     SecurityWrapper(unsigned flags);
 
+    virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool preventExtensions(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
     virtual bool enter(JSContext *cx, HandleObject wrapper, HandleId id, Wrapper::Action act,
                        bool *bp) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) 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,
@@ -167,28 +171,30 @@ typedef SecurityWrapper<CrossCompartment
 class JS_FRIEND_API(DeadObjectProxy) : public BaseProxyHandler
 {
   public:
     static int sDeadObjectFamily;
 
     explicit DeadObjectProxy();
 
     /* ES5 Harmony fundamental wrapper traps. */
+    virtual bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                        PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool getOwnPropertyDescriptor(JSContext *cx, HandleObject wrapper, HandleId id,
                                           PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE;
     virtual bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                 PropertyDescriptor *desc) MOZ_OVERRIDE;
     virtual bool getOwnPropertyNames(JSContext *cx, HandleObject wrapper,
                                      AutoIdVector &props) MOZ_OVERRIDE;
     virtual bool delete_(JSContext *cx, HandleObject wrapper, HandleId id, bool *bp) MOZ_OVERRIDE;
     virtual bool enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
+    virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, unsigned argc, Value *vp);
     virtual bool construct(JSContext *cx, HandleObject proxy, unsigned argc,
                            Value *argv, MutableHandleValue rval);
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
     virtual JSString *obj_toString(JSContext *cx, HandleObject proxy);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_5/extensions/preventExtensions-cross-global.js
@@ -0,0 +1,49 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs newGlobal()
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ * Contributor:
+ *   Jeff Walden <jwalden+code@mit.edu>
+ */
+
+var gTestfile = 'preventExtensions-cross-global.js';
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 789897;
+var summary =
+  "Object.preventExtensions and Object.isExtensible should work correctly " +
+  "across globals";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+var otherGlobal = newGlobal();
+
+var obj = {};
+assertEq(otherGlobal.Object.isExtensible(obj), true);
+assertEq(otherGlobal.Object.preventExtensions(obj), obj);
+assertEq(otherGlobal.Object.isExtensible(obj), false);
+
+var objFromOther = otherGlobal.Object();
+assertEq(Object.isExtensible(objFromOther), true);
+assertEq(Object.preventExtensions(objFromOther), objFromOther);
+assertEq(Object.isExtensible(objFromOther), false);
+
+var proxy = new Proxy({}, {});
+assertEq(otherGlobal.Object.isExtensible(proxy), true);
+assertEq(otherGlobal.Object.preventExtensions(proxy), proxy);
+assertEq(otherGlobal.Object.isExtensible(proxy), false);
+
+var proxyFromOther = otherGlobal.evaluate("new Proxy({}, {})");
+assertEq(Object.isExtensible(proxyFromOther), true);
+assertEq(Object.preventExtensions(proxyFromOther), proxyFromOther);
+assertEq(Object.isExtensible(proxyFromOther), false);
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -109,16 +109,17 @@
     macro(numeric, numeric, "numeric") \
     macro(objectNull, objectNull, "[object Null]") \
     macro(objectUndefined, objectUndefined, "[object Undefined]") \
     macro(of, of, "of") \
     macro(offset, offset, "offset") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
     macro(pattern, pattern, "pattern") \
+    macro(preventExtensions, preventExtensions, "preventExtensions") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
     macro(proto, proto, "__proto__") \
     macro(return, return_, "return") \
     macro(sensitivity, sensitivity, "sensitivity") \
     macro(set, set, "set") \
     macro(shape, shape, "shape") \
     macro(source, source, "source") \
     macro(stack, stack, "stack") \
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4494,17 +4494,17 @@ DebuggerObject_sealHelper(JSContext *cx,
     } else if (op == Freeze) {
         ok = JSObject::freeze(cx, obj);
     } else {
         JS_ASSERT(op == PreventExtensions);
         if (!obj->isExtensible()) {
             args.rval().setUndefined();
             return true;
         }
-        ok = obj->preventExtensions(cx);
+        ok = JSObject::preventExtensions(cx, obj);
     }
     if (!ok)
         return false;
     args.rval().setUndefined();
     return true;
 }
 
 static JSBool
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -371,17 +371,17 @@ GlobalObject::initFunctionAndObjectClass
         return NULL;
     self->setOriginalEval(evalobj);
 
     /* ES5 13.2.3: Construct the unique [[ThrowTypeError]] function object. */
     RootedFunction throwTypeError(cx, NewFunction(cx, NullPtr(), ThrowTypeError, 0,
                                                   JSFunction::NATIVE_FUN, self, NullPtr()));
     if (!throwTypeError)
         return NULL;
-    if (!throwTypeError->preventExtensions(cx))
+    if (!JSObject::preventExtensions(cx, throwTypeError))
         return NULL;
     self->setThrowTypeError(throwTypeError);
 
     RootedObject intrinsicsHolder(cx);
     if (cx->runtime->isSelfHostingGlobal(self)) {
         intrinsicsHolder = self;
     } else {
         intrinsicsHolder = NewObjectWithClassProto(cx, &ObjectClass, NULL, self);
--- a/js/src/vm/ObjectImpl-inl.h
+++ b/js/src/vm/ObjectImpl-inl.h
@@ -8,16 +8,17 @@
 #ifndef ObjectImpl_inl_h___
 #define ObjectImpl_inl_h___
 
 #include "mozilla/Assertions.h"
 
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jsinterp.h"
+#include "jsproxy.h"
 
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "js/TemplateLib.h"
 
 #include "ObjectImpl.h"
 
 namespace js {
@@ -80,16 +81,20 @@ inline bool
 js::ObjectImpl::nativeContains(JSContext *cx, Shape *shape)
 {
     return nativeLookup(cx, shape->propid()) == shape;
 }
 
 inline bool
 js::ObjectImpl::isExtensible() const
 {
+    if (this->isProxy())
+        return Proxy::isExtensible(const_cast<JSObject*>(this->asObjectPtr()));
+
+    // [[Extensible]] for ordinary non-proxy objects is an object flag.
     return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE);
 }
 
 inline uint32_t
 js::ObjectImpl::getDenseInitializedLength()
 {
     MOZ_ASSERT(isNative());
     return getElementsHeader()->initializedLength;
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1107,28 +1107,35 @@ class ObjectImpl : public gc::Cell
     }
 
     Class *getClass() const {
         return type_->clasp;
     }
 
     inline bool isExtensible() const;
 
+    // Attempt to change the [[Extensible]] bit on |obj| to false.  Callers
+    // must ensure that |obj| is currently extensible before calling this!
+    static bool
+    preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj);
+
     inline HeapSlotArray getDenseElements();
     inline const Value & getDenseElement(uint32_t idx);
     inline bool containsDenseElement(uint32_t idx);
     inline uint32_t getDenseInitializedLength();
 
     bool makeElementsSparse(JSContext *cx) {
         NEW_OBJECT_REPRESENTATION_ONLY();
 
         MOZ_NOT_REACHED("NYI");
         return false;
     }
 
+    inline bool isProxy() const;
+
   protected:
 #ifdef DEBUG
     void checkShapeConsistency();
 #else
     void checkShapeConsistency() { }
 #endif
 
     Shape *
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -1302,16 +1302,30 @@ class DebugScopeProxy : public BaseProxy
     }
 
   public:
     static int family;
     static DebugScopeProxy singleton;
 
     DebugScopeProxy() : BaseProxyHandler(&family) {}
 
+    bool isExtensible(JSObject *proxy) MOZ_OVERRIDE
+    {
+        // always [[Extensible]], can't be made non-[[Extensible]], like most
+        // proxies
+        return true;
+    }
+
+    bool preventExtensions(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE
+    {
+        // See above.
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+        return false;
+    }
+
     bool getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id, PropertyDescriptor *desc,
                                unsigned flags) MOZ_OVERRIDE
     {
         return getOwnPropertyDescriptor(cx, proxy, id, desc, flags);
     }
 
     bool getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                   PropertyDescriptor *desc, unsigned flags) MOZ_OVERRIDE
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1023,22 +1023,29 @@ Shape::setObjectParent(JSContext *cx, JS
 
     StackBaseShape base(last);
     base.parent = parent;
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
-bool
-JSObject::preventExtensions(JSContext *cx)
+/* static */ bool
+js::ObjectImpl::preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj)
 {
-    JS_ASSERT(isExtensible());
+    MOZ_ASSERT(obj->isExtensible(),
+               "Callers must ensure |obj| is extensible before calling "
+               "preventExtensions");
 
-    RootedObject self(cx, this);
+    if (obj->isProxy()) {
+        RootedObject object(cx, obj->asObjectPtr());
+        return js::Proxy::preventExtensions(cx, object);
+    }
+
+    RootedObject self(cx, obj->asObjectPtr());
 
     /*
      * Force lazy properties to be resolved by iterating over the objects' own
      * properties.
      */
     AutoIdVector props(cx);
     if (!js::GetPropertyNames(cx, self, JSITER_HIDDEN | JSITER_OWNONLY, &props))
         return false;
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1332,17 +1332,17 @@ IsXrayResolving(JSContext *cx, JSObject 
     {
         return false;
     }
     JSObject *holder =
       XPCWrappedNativeXrayTraits::singleton.ensureHolder(cx, wrapper);
     return XPCWrappedNativeXrayTraits::isResolving(cx, holder, id);
 }
 
-}
+} // namespace XrayUtils
 
 static JSBool
 XrayToString(JSContext *cx, unsigned argc, jsval *vp)
 {
     JSObject *wrapper = JS_THIS_OBJECT(cx, vp);
     if (!wrapper)
         return false;
     if (IsWrapper(wrapper) &&
@@ -1413,16 +1413,37 @@ DEBUG_CheckXBLLookup(JSContext *cx, JSPr
     }
 }
 #else
 #define DEBUG_CheckXBLLookup(a, b) {}
 #endif
 
 template <typename Base, typename Traits>
 bool
+XrayWrapper<Base, Traits>::isExtensible(JSObject *wrapper)
+{
+    // Xray wrappers are supposed to provide a clean view of the target
+    // reflector, hiding any modifications by script in the target scope.  So
+    // even if that script freezes the reflector, we don't want to make that
+    // visible to the caller. DOM reflectors are always extensible by default,
+    // so we can just return true here.
+    return true;
+}
+
+template <typename Base, typename Traits>
+bool
+XrayWrapper<Base, Traits>::preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper)
+{
+    // See above.
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CHANGE_EXTENSIBILITY);
+    return false;
+}
+
+template <typename Base, typename Traits>
+bool
 XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper,
                                                  JS::Handle<jsid> id,
                                                  js::PropertyDescriptor *desc, unsigned flags)
 {
     assertEnteredPolicy(cx, wrapper, id);
     JSObject *holder = Traits::singleton.ensureHolder(cx, wrapper);
     if (Traits::isResolving(cx, holder, id)) {
         desc->obj = NULL;
--- a/js/xpconnect/wrappers/XrayWrapper.h
+++ b/js/xpconnect/wrappers/XrayWrapper.h
@@ -61,16 +61,18 @@ XrayTraits* GetXrayTraits(JSObject *obj)
 // NB: Base *must* derive from JSProxyHandler
 template <typename Base, typename Traits = XPCWrappedNativeXrayTraits >
 class XrayWrapper : public Base {
   public:
     XrayWrapper(unsigned flags);
     virtual ~XrayWrapper();
 
     /* Fundamental proxy traps. */
+    virtual bool isExtensible(JSObject *wrapper) MOZ_OVERRIDE;
+    virtual bool preventExtensions(JSContext *cx, JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
     virtual bool getPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                        js::PropertyDescriptor *desc, unsigned flags);
     virtual bool getOwnPropertyDescriptor(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                           js::PropertyDescriptor *desc,
                                           unsigned flags);
     virtual bool defineProperty(JSContext *cx, JS::Handle<JSObject*> wrapper, JS::Handle<jsid> id,
                                 js::PropertyDescriptor *desc);
     virtual bool getOwnPropertyNames(JSContext *cx, JS::Handle<JSObject*> wrapper,