Bug 550356 - Enumeration over COWs should work. r=jst
authorBlake Kaplan <mrbkap@gmail.com>
Fri, 05 Mar 2010 02:03:05 -0800
changeset 39407 333589ef694be3a8048ac985b07a19d055b0cd6f
parent 39406 8dc04a747e92d63e42815ef12717603d30f804a2
child 39408 cf304a5095c7dfc7a7ae8e000842606dddda290a
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
bugs550356
milestone1.9.3a3pre
Bug 550356 - Enumeration over COWs should work. r=jst
js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
js/src/xpconnect/src/XPCWrapper.cpp
js/src/xpconnect/src/XPCWrapper.h
js/src/xpconnect/tests/mochitest/test_cows.html
--- a/js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
+++ b/js/src/xpconnect/src/XPCChromeObjectWrapper.cpp
@@ -769,33 +769,33 @@ XPC_COW_Iterator(JSContext *cx, JSObject
   }
 
   XPCCallContext ccx(JS_CALLER, cx);
   if (!ccx.IsValid()) {
     ThrowException(NS_ERROR_FAILURE, cx);
     return nsnull;
   }
 
-  JSObject *wrapperIter = JS_NewObject(cx, &COWClass.base, nsnull,
-                                       JS_GetGlobalForObject(cx, obj));
-  if (!wrapperIter) {
-    return nsnull;
+  jsval exposedProps;
+  if (!JS_GetReservedSlot(cx, obj, sExposedPropsSlot, &exposedProps)) {
+    return JS_FALSE;
   }
 
-  JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(wrapperIter));
-
-  // Initialize our COW.
-  jsval v = OBJECT_TO_JSVAL(wrappedObj);
-  if (!JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sWrappedObjSlot, v) ||
-      !JS_SetReservedSlot(cx, wrapperIter, XPCWrapper::sFlagsSlot,
-                          JSVAL_ZERO)) {
-    return nsnull;
+  JSObject *propertyContainer;
+  if (JSVAL_IS_VOID(exposedProps)) {
+    // TODO For now, expose whatever properties are on our object.
+    propertyContainer = wrappedObj;
+  } else if (JSVAL_IS_PRIMITIVE(exposedProps)) {
+    // Expose no properties at all.
+    propertyContainer = nsnull;
+  } else {
+    // Just expose whatever the object exposes through __exposedProps__.
+    propertyContainer = JSVAL_TO_OBJECT(exposedProps);
   }
 
-  return XPCWrapper::CreateIteratorObj(cx, wrapperIter, obj, wrappedObj,
-                                       keysonly);
+  return CreateSimpleIterator(cx, obj, keysonly, propertyContainer);
 }
 
 static JSObject *
 XPC_COW_WrappedObject(JSContext *cx, JSObject *obj)
 {
   return GetWrappedObject(cx, obj);
 }
--- a/js/src/xpconnect/src/XPCWrapper.cpp
+++ b/js/src/xpconnect/src/XPCWrapper.cpp
@@ -117,16 +117,19 @@ IteratorNext(JSContext *cx, uintN argc, 
   jsval v;
 
   obj = JS_THIS_OBJECT(cx, vp);
   if (!obj)
     return JS_FALSE;
 
   JS_GetReservedSlot(cx, obj, 0, &v);
   JSIdArray *ida = reinterpret_cast<JSIdArray *>(JSVAL_TO_PRIVATE(v));
+  if (!ida) {
+    return JS_ThrowStopIteration(cx);
+  }
 
   JS_GetReservedSlot(cx, obj, 1, &v);
   jsint idx = JSVAL_TO_INT(v);
 
   if (idx == ida->length) {
     return JS_ThrowStopIteration(cx);
   }
 
@@ -137,35 +140,19 @@ IteratorNext(JSContext *cx, uintN argc, 
     if (!JS_IdToValue(cx, id, &v) ||
         !(str = JS_ValueToString(cx, v))) {
       return JS_FALSE;
     }
 
     *vp = STRING_TO_JSVAL(str);
   } else {
     // We need to return an [id, value] pair.
-    if (!JS_GetPropertyById(cx, STOBJ_GET_PARENT(obj), id, &v)) {
-      return JS_FALSE;
-    }
-
-    jsval name;
-    JSString *str;
-    if (!JS_IdToValue(cx, id, &name) ||
-        !(str = JS_ValueToString(cx, name))) {
+    if (!JS_GetPropertyById(cx, STOBJ_GET_PARENT(obj), id, vp)) {
       return JS_FALSE;
     }
-
-    jsval vec[2] = { STRING_TO_JSVAL(str), v };
-    JSAutoTempValueRooter tvr(cx, 2, vec);
-    JSObject *array = JS_NewArrayObject(cx, 2, vec);
-    if (!array) {
-      return JS_FALSE;
-    }
-
-    *vp = OBJECT_TO_JSVAL(array);
   }
 
   JS_SetReservedSlot(cx, obj, 1, INT_TO_JSVAL(idx));
   return JS_TRUE;
 }
 
 static JSClass IteratorClass = {
   "XOW iterator", JSCLASS_HAS_RESERVED_SLOTS(3),
@@ -286,28 +273,56 @@ CreateWrapperFromType(JSContext *cx, JSO
         return JS_FALSE;
       }
     }
   }
 
   return JS_TRUE;
 }
 
+static JSObject *
+FinishCreatingIterator(JSContext *cx, JSObject *iterObj, JSBool keysonly)
+{
+  JSIdArray *ida = JS_Enumerate(cx, iterObj);
+  if (!ida) {
+    return nsnull;
+  }
+
+  // Initialize iterObj.
+  if (!JS_DefineFunction(cx, iterObj, "next", (JSNative)IteratorNext, 0,
+                         JSFUN_FAST_NATIVE)) {
+    return nsnull;
+  }
+
+  if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(ida)) ||
+      !JS_SetReservedSlot(cx, iterObj, 1, JSVAL_ZERO) ||
+      !JS_SetReservedSlot(cx, iterObj, 2, BOOLEAN_TO_JSVAL(keysonly))) {
+    return nsnull;
+  }
+
+  if (!JS_SetPrototype(cx, iterObj, nsnull)) {
+    return nsnull;
+  }
+
+  return iterObj;
+}
+
 JSObject *
 CreateIteratorObj(JSContext *cx, JSObject *tempWrapper,
                   JSObject *wrapperObj, JSObject *innerObj,
                   JSBool keysonly)
 {
   // This is rather ugly: we want to use the trick seen in Enumerate,
   // where we use our wrapper's resolve hook to determine if we should
   // enumerate a given property. However, we don't want to pollute the
   // identifiers with a next method, so we create an object that
   // delegates (via the __proto__ link) to the wrapper.
 
-  JSObject *iterObj = JS_NewObject(cx, &IteratorClass, tempWrapper, wrapperObj);
+  JSObject *iterObj =
+    JS_NewObjectWithGivenProto(cx, &IteratorClass, tempWrapper, wrapperObj);
   if (!iterObj) {
     return nsnull;
   }
 
   JSAutoTempValueRooter tvr(cx, OBJECT_TO_JSVAL(iterObj));
 
   // Do this sooner rather than later to avoid complications in
   // IteratorFinalize.
@@ -332,38 +347,73 @@ CreateIteratorObj(JSContext *cx, JSObjec
 
   // Start enumerating over all of our properties.
   do {
     if (!XPCWrapper::Enumerate(cx, iterObj, innerObj)) {
       return nsnull;
     }
   } while ((innerObj = STOBJ_GET_PROTO(innerObj)) != nsnull);
 
-  JSIdArray *ida = JS_Enumerate(cx, iterObj);
+  return FinishCreatingIterator(cx, iterObj, keysonly);
+}
+
+static JSBool
+SimpleEnumerate(JSContext *cx, JSObject *iterObj, JSObject *properties)
+{
+  JSIdArray *ida = JS_Enumerate(cx, properties);
   if (!ida) {
-    return nsnull;
+    return JS_FALSE;
   }
 
-  // Initialize iterObj.
-  if (!JS_DefineFunction(cx, iterObj, "next", (JSNative)IteratorNext, 0,
-                         JSFUN_FAST_NATIVE)) {
+  for (jsint i = 0, n = ida->length; i < n; ++i) {
+    if (!JS_DefinePropertyById(cx, iterObj, ida->vector[i], JSVAL_VOID,
+                               nsnull, nsnull,
+                               JSPROP_ENUMERATE | JSPROP_SHARED)) {
+      return JS_FALSE;
+    }
+  }
+
+  JS_DestroyIdArray(cx, ida);
+
+  return JS_TRUE;
+}
+
+JSObject *
+CreateSimpleIterator(JSContext *cx, JSObject *scope, JSBool keysonly,
+                     JSObject *propertyContainer)
+{
+  JSObject *iterObj = JS_NewObjectWithGivenProto(cx, &IteratorClass,
+                                                 propertyContainer, scope);
+  if (!iterObj) {
     return nsnull;
   }
 
-  if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(ida)) ||
-      !JS_SetReservedSlot(cx, iterObj, 1, JSVAL_ZERO) ||
-      !JS_SetReservedSlot(cx, iterObj, 2, BOOLEAN_TO_JSVAL(keysonly))) {
-    return nsnull;
+  JSAutoTempValueRooter tvr(cx, iterObj);
+  if (!propertyContainer) {
+    if (!JS_SetReservedSlot(cx, iterObj, 0, PRIVATE_TO_JSVAL(nsnull)) ||
+        !JS_SetReservedSlot(cx, iterObj, 1, JSVAL_ZERO) ||
+        !JS_SetReservedSlot(cx, iterObj, 2, JSVAL_TRUE)) {
+      return nsnull;
+    }
+
+    if (!JS_DefineFunction(cx, iterObj, "next", (JSNative)IteratorNext, 0,
+                           JSFUN_FAST_NATIVE)) {
+      return nsnull;
+    }
+
+    return iterObj;
   }
 
-  if (!JS_SetPrototype(cx, iterObj, nsnull)) {
-    return nsnull;
-  }
+  do {
+    if (!SimpleEnumerate(cx, iterObj, propertyContainer)) {
+      return nsnull;
+    }
+  } while ((propertyContainer = STOBJ_GET_PROTO(propertyContainer)));
 
-  return iterObj;
+  return FinishCreatingIterator(cx, iterObj, keysonly);
 }
 
 JSBool
 AddProperty(JSContext *cx, JSObject *wrapperObj, JSBool wantGetterSetter,
             JSObject *innerObj, jsval id, jsval *vp)
 {
   jsid interned_id;
   if (!::JS_ValueToId(cx, id, &interned_id)) {
--- a/js/src/xpconnect/src/XPCWrapper.h
+++ b/js/src/xpconnect/src/XPCWrapper.h
@@ -438,16 +438,24 @@ CreateWrapperFromType(JSContext *cx, JSO
  * is expected to censor it. tempWrapper must be rooted already.
  */
 JSObject *
 CreateIteratorObj(JSContext *cx, JSObject *tempWrapper,
                   JSObject *wrapperObj, JSObject *innerObj,
                   JSBool keysonly);
 
 /**
+ * Like CreateIteratorObj, but doesn't need a security wrapper. If
+ * propertyContainer is null, creates an empty iterator.
+ */
+JSObject *
+CreateSimpleIterator(JSContext *cx, JSObject *scope, JSBool keysonly,
+                     JSObject *propertyContainer);
+
+/**
  * Called for the common part of adding a property to obj.
  */
 JSBool
 AddProperty(JSContext *cx, JSObject *wrapperObj,
             JSBool wantGetterSetter, JSObject *innerObj,
             jsval id, jsval *vp);
 
 /**
--- a/js/src/xpconnect/tests/mochitest/test_cows.html
+++ b/js/src/xpconnect/tests/mochitest/test_cows.html
@@ -127,20 +127,20 @@ function COWTests() {
         ok(false, "deleting a read-only exposed prop shouldn't work");
     } catch (e) {
         ok(/SECURITY_MANAGER/.test(e),
            "deleting a read-only exposed prop should throw error");
     }
 
     try {
         var props = [name for (name in getCOW(readable))];
-        todo_is(props.length, 1,
-                "COW w/ one exposed prop should enumerate once");
-        todo_is(props[0], 'foo',
-                "COW w/ one exposed prop should enumerate it");
+        is(props.length, 1, "COW w/ one exposed prop should enumerate once");
+        is(props[0], 'foo', "COW w/ one exposed prop should enumerate it");
+        props = [value for each (value in getCOW(readable))];
+        is(props[0], 5, "for-each over COWs works");
     } catch (e) {
         ok(false, "COW w/ a readable prop should not raise exc " +
                   "on enumeration: " + e);
     }
 
     try {
         var COWFunc = getCOW((function() { return 5; }));
         is(COWFunc(), 5, "COWed functions should be callable");