Bug 789897 - Add scripting support + tests for isExtensible, preventExtensions traps. r=jwalden, r=jorendorff
authorEddy Bruel <ejpbruel@mozilla.com>
Fri, 22 Mar 2013 19:43:12 -0700
changeset 136970 2e564ec4c11d2cbc37a43e1247c4c72078ebcb24
parent 136969 804c5ca59d99e89c7ee1e1d223a6ceafd34e0b65
child 136971 321ce1200edea58447113365dd89f6257136a664
push idunknown
push userunknown
push dateunknown
reviewersjwalden, jorendorff
bugs789897
milestone22.0a1
Bug 789897 - Add scripting support + tests for isExtensible, preventExtensions traps. r=jwalden, r=jorendorff
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/jsproxy.cpp
js/src/vm/CommonPropertyNames.h
--- 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/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -1039,16 +1039,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;
@@ -1607,16 +1608,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))
--- 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") \