Bug 725907 - for-of improvements, part 4: Add .next() method to iterator objects and make for-of call it. r=bhackett.
This commit is contained in:
@@ -51,31 +51,6 @@ using namespace mozilla;
|
||||
using namespace js;
|
||||
using namespace js::gc;
|
||||
|
||||
Class js::ElementIteratorClass = {
|
||||
"ElementIterator",
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots),
|
||||
JS_PropertyStub, /* addProperty */
|
||||
JS_PropertyStub, /* delProperty */
|
||||
JS_PropertyStub, /* getProperty */
|
||||
JS_StrictPropertyStub, /* setProperty */
|
||||
JS_EnumerateStub,
|
||||
JS_ResolveStub,
|
||||
JS_ConvertStub,
|
||||
NULL, /* finalize */
|
||||
NULL, /* checkAccess */
|
||||
NULL, /* call */
|
||||
NULL, /* construct */
|
||||
NULL, /* hasInstance */
|
||||
NULL, /* trace */
|
||||
{
|
||||
NULL, /* equality */
|
||||
NULL, /* outerObject */
|
||||
NULL, /* innerObject */
|
||||
NULL, /* iteratorObject */
|
||||
NULL /* unused */
|
||||
}
|
||||
};
|
||||
|
||||
static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2;
|
||||
|
||||
void
|
||||
@@ -737,6 +712,18 @@ GetIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp)
|
||||
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_ThrowStopIteration(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!JS_IsExceptionPending(cx));
|
||||
RootedValue v(cx);
|
||||
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
|
||||
cx->setPendingException(v);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
/*** Iterator objects ****************************************************************************/
|
||||
|
||||
static JSBool
|
||||
Iterator(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
@@ -757,16 +744,6 @@ Iterator(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_ThrowStopIteration(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(!JS_IsExceptionPending(cx));
|
||||
RootedValue v(cx);
|
||||
if (js_FindClassObject(cx, NullPtr(), JSProto_StopIteration, &v))
|
||||
cx->setPendingException(v);
|
||||
return JS_FALSE;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsIterator(const Value &v)
|
||||
{
|
||||
@@ -812,7 +789,7 @@ static JSFunctionSpec iterator_methods[] = {
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
JSObject *
|
||||
static JSObject *
|
||||
iterator_iteratorObject(JSContext *cx, HandleObject obj, JSBool keysonly)
|
||||
{
|
||||
return obj;
|
||||
@@ -861,6 +838,105 @@ Class PropertyIteratorObject::class_ = {
|
||||
}
|
||||
};
|
||||
|
||||
const uint32_t CLOSED_INDEX = UINT32_MAX;
|
||||
|
||||
JSObject *
|
||||
ElementIteratorObject::create(JSContext *cx, Handle<Value> target)
|
||||
{
|
||||
GlobalObject *global = GetCurrentGlobal(cx);
|
||||
Rooted<JSObject*> proto(cx, global->getOrCreateElementIteratorPrototype(cx));
|
||||
if (!proto)
|
||||
return NULL;
|
||||
JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, proto, global);
|
||||
if (iterobj) {
|
||||
iterobj->setReservedSlot(TargetSlot, target);
|
||||
iterobj->setReservedSlot(IndexSlot, Int32Value(0));
|
||||
}
|
||||
return iterobj;
|
||||
}
|
||||
|
||||
static bool
|
||||
IsElementIterator(const Value &v)
|
||||
{
|
||||
return v.isObject() && v.toObject().isElementIterator();
|
||||
}
|
||||
|
||||
JSBool
|
||||
ElementIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return CallNonGenericMethod(cx, IsElementIterator, next_impl, args);
|
||||
}
|
||||
|
||||
bool
|
||||
ElementIteratorObject::next_impl(JSContext *cx, CallArgs args)
|
||||
{
|
||||
JSObject *iterobj = &args.thisv().toObject();
|
||||
uint32_t i, length;
|
||||
Value target = iterobj->getReservedSlot(TargetSlot);
|
||||
Rooted<JSObject*> obj(cx);
|
||||
|
||||
// Get target.length.
|
||||
if (target.isString()) {
|
||||
length = uint32_t(target.toString()->length());
|
||||
} else {
|
||||
obj = ValueToObject(cx, target);
|
||||
if (!obj)
|
||||
goto close;
|
||||
if (!js_GetLengthProperty(cx, obj, &length))
|
||||
goto close;
|
||||
}
|
||||
|
||||
// Check target.length.
|
||||
i = uint32_t(iterobj->getReservedSlot(IndexSlot).toInt32());
|
||||
if (i >= length) {
|
||||
js_ThrowStopIteration(cx);
|
||||
goto close;
|
||||
}
|
||||
|
||||
// Get target[i].
|
||||
JS_ASSERT(i + 1 > i);
|
||||
if (target.isString()) {
|
||||
JSString *c = cx->runtime->staticStrings.getUnitStringForElement(cx, target.toString(), i);
|
||||
if (!c)
|
||||
goto close;
|
||||
args.rval().setString(c);
|
||||
} else {
|
||||
if (!obj->getElement(cx, obj, i, &args.rval()))
|
||||
goto close;
|
||||
}
|
||||
|
||||
// On success, bump the index.
|
||||
iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(i + 1)));
|
||||
return true;
|
||||
|
||||
close:
|
||||
// Close the iterator. The TargetSlot will never be used again, so don't keep a
|
||||
// reference to it.
|
||||
iterobj->setReservedSlot(TargetSlot, UndefinedValue());
|
||||
iterobj->setReservedSlot(IndexSlot, Int32Value(int32_t(CLOSED_INDEX)));
|
||||
return false;
|
||||
}
|
||||
|
||||
Class js::ElementIteratorClass = {
|
||||
"Iterator",
|
||||
JSCLASS_IMPLEMENTS_BARRIERS |
|
||||
JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots),
|
||||
JS_PropertyStub, /* addProperty */
|
||||
JS_PropertyStub, /* delProperty */
|
||||
JS_PropertyStub, /* getProperty */
|
||||
JS_StrictPropertyStub, /* setProperty */
|
||||
JS_EnumerateStub,
|
||||
JS_ResolveStub,
|
||||
JS_ConvertStub,
|
||||
NULL /* finalize */
|
||||
};
|
||||
|
||||
JSFunctionSpec ElementIteratorObject::methods[] = {
|
||||
JS_FN("next", next, 0, 0),
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
static JSBool
|
||||
CloseGenerator(JSContext *cx, JSObject *genobj);
|
||||
@@ -1103,75 +1179,6 @@ js_SuppressDeletedElements(JSContext *cx, HandleObject obj, uint32_t begin, uint
|
||||
return SuppressDeletedPropertyHelper(cx, obj, IndexRangePredicate(begin, end));
|
||||
}
|
||||
|
||||
const uint32_t CLOSED_INDEX = UINT32_MAX;
|
||||
|
||||
JSObject *
|
||||
ElementIteratorObject::create(JSContext *cx, HandleObject obj)
|
||||
{
|
||||
JS_ASSERT(obj);
|
||||
JSObject *iterobj = NewObjectWithGivenProto(cx, &ElementIteratorClass, NULL, obj);
|
||||
if (iterobj) {
|
||||
iterobj->setReservedSlot(TargetSlot, ObjectValue(*obj));
|
||||
iterobj->setReservedSlot(IndexSlot, Int32Value(0));
|
||||
}
|
||||
return iterobj;
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
ElementIteratorObject::getIndex() const
|
||||
{
|
||||
return uint32_t(getReservedSlot(IndexSlot).toInt32());
|
||||
}
|
||||
|
||||
inline JSObject *
|
||||
ElementIteratorObject::getTargetObject() const
|
||||
{
|
||||
return &getReservedSlot(TargetSlot).toObject();
|
||||
}
|
||||
|
||||
inline void
|
||||
ElementIteratorObject::setIndex(uint32_t index)
|
||||
{
|
||||
setReservedSlot(IndexSlot, Int32Value(int32_t(index)));
|
||||
}
|
||||
|
||||
bool
|
||||
ElementIteratorObject::iteratorNext(JSContext *cx, Value *vp)
|
||||
{
|
||||
Rooted<ElementIteratorObject*> self(cx, this);
|
||||
|
||||
uint32_t i, length;
|
||||
RootedObject obj(cx, getTargetObject());
|
||||
if (!js_GetLengthProperty(cx, obj, &length))
|
||||
goto error;
|
||||
|
||||
i = self->getIndex();
|
||||
if (i >= length) {
|
||||
self->setIndex(CLOSED_INDEX);
|
||||
vp->setMagic(JS_NO_ITER_VALUE);
|
||||
return true;
|
||||
}
|
||||
|
||||
JS_ASSERT(i + 1 > i);
|
||||
if (!obj->getElement(cx, obj, i, vp))
|
||||
goto error;
|
||||
|
||||
/* On success, bump the index. */
|
||||
self->setIndex(i + 1);
|
||||
return true;
|
||||
|
||||
error:
|
||||
self->setIndex(CLOSED_INDEX);
|
||||
return false;
|
||||
}
|
||||
|
||||
inline js::ElementIteratorObject *
|
||||
JSObject::asElementIterator()
|
||||
{
|
||||
JS_ASSERT(isElementIterator());
|
||||
return static_cast<js::ElementIteratorObject *>(this);
|
||||
}
|
||||
|
||||
JSBool
|
||||
js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval)
|
||||
{
|
||||
@@ -1207,25 +1214,6 @@ js_IteratorMore(JSContext *cx, HandleObject iterobj, Value *rval)
|
||||
return false;
|
||||
if ((ni->flags & JSITER_KEYVALUE) && !NewKeyValuePair(cx, id, *rval, rval))
|
||||
return false;
|
||||
} else if (iterobj->isElementIterator()) {
|
||||
/*
|
||||
* Like native iterators, element iterators do not have a .next
|
||||
* method, so this fast path is necessary for correctness.
|
||||
*/
|
||||
if (!iterobj->asElementIterator()->iteratorNext(cx, rval))
|
||||
return false;
|
||||
if (rval->isMagic(JS_NO_ITER_VALUE)) {
|
||||
cx->iterValue.setMagic(JS_NO_ITER_VALUE);
|
||||
rval->setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
} else if (iterobj->isProxy()) {
|
||||
if (!Proxy::iteratorNext(cx, iterobj, rval))
|
||||
return false;
|
||||
if (rval->isMagic(JS_NO_ITER_VALUE)) {
|
||||
rval->setBoolean(false);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
/* Call the iterator object's .next method. */
|
||||
if (!GetMethod(cx, iterobj, cx->runtime->atomState.nextAtom, 0, rval))
|
||||
@@ -1314,6 +1302,8 @@ Class js::StopIterationClass = {
|
||||
NULL /* construct */
|
||||
};
|
||||
|
||||
/*** Generators **********************************************************************************/
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
|
||||
static void
|
||||
@@ -1756,83 +1746,75 @@ static JSFunctionSpec generator_methods[] = {
|
||||
|
||||
#endif /* JS_HAS_GENERATORS */
|
||||
|
||||
static bool
|
||||
InitIteratorClass(JSContext *cx, Handle<GlobalObject*> global)
|
||||
{
|
||||
Rooted<JSObject*> iteratorProto(cx,
|
||||
global->createBlankPrototype(cx, &PropertyIteratorObject::class_));
|
||||
if (!iteratorProto)
|
||||
return false;
|
||||
|
||||
AutoIdVector blank(cx);
|
||||
NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank);
|
||||
if (!ni)
|
||||
return false;
|
||||
ni->init(NULL, 0 /* flags */, 0, 0);
|
||||
|
||||
iteratorProto->asPropertyIterator().setNativeIterator(ni);
|
||||
|
||||
RootedFunction ctor(cx, global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2));
|
||||
if (!ctor)
|
||||
return false;
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
|
||||
return false;
|
||||
|
||||
if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods))
|
||||
return false;
|
||||
|
||||
return DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
GlobalObject::initGeneratorClass(JSContext *cx, Handle<GlobalObject*> global)
|
||||
GlobalObject::initIteratorClasses(JSContext *cx, Handle<GlobalObject *> global)
|
||||
{
|
||||
Rooted<JSObject*> iteratorProto(cx);
|
||||
Value iteratorProtoVal = global->getPrototype(JSProto_Iterator);
|
||||
if (iteratorProtoVal.isObject()) {
|
||||
iteratorProto = &iteratorProtoVal.toObject();
|
||||
} else {
|
||||
iteratorProto = global->createBlankPrototype(cx, &PropertyIteratorObject::class_);
|
||||
if (!iteratorProto)
|
||||
return false;
|
||||
|
||||
AutoIdVector blank(cx);
|
||||
NativeIterator *ni = NativeIterator::allocateIterator(cx, 0, blank);
|
||||
if (!ni)
|
||||
return false;
|
||||
ni->init(NULL, 0 /* flags */, 0, 0);
|
||||
|
||||
iteratorProto->asPropertyIterator().setNativeIterator(ni);
|
||||
|
||||
Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, Iterator, CLASS_NAME(cx, Iterator), 2));
|
||||
if (!ctor)
|
||||
return false;
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, iteratorProto))
|
||||
return false;
|
||||
if (!DefinePropertiesAndBrand(cx, iteratorProto, NULL, iterator_methods))
|
||||
return false;
|
||||
if (!DefineConstructorAndPrototype(cx, global, JSProto_Iterator, ctor, iteratorProto))
|
||||
return false;
|
||||
}
|
||||
|
||||
Rooted<JSObject*> proto(cx);
|
||||
if (global->getSlot(ELEMENT_ITERATOR_PROTO).isUndefined()) {
|
||||
Class *cls = &ElementIteratorClass;
|
||||
proto = global->createBlankPrototypeInheriting(cx, cls, *iteratorProto);
|
||||
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, ElementIteratorObject::methods))
|
||||
return false;
|
||||
global->setReservedSlot(ELEMENT_ITERATOR_PROTO, ObjectValue(*proto));
|
||||
}
|
||||
|
||||
#if JS_HAS_GENERATORS
|
||||
RootedObject proto(cx, global->createBlankPrototype(cx, &GeneratorClass));
|
||||
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
|
||||
return false;
|
||||
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
|
||||
if (global->getSlot(GENERATOR_PROTO).isUndefined()) {
|
||||
proto = global->createBlankPrototype(cx, &GeneratorClass);
|
||||
if (!proto || !DefinePropertiesAndBrand(cx, proto, NULL, generator_methods))
|
||||
return false;
|
||||
global->setReservedSlot(GENERATOR_PROTO, ObjectValue(*proto));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (global->getPrototype(JSProto_StopIteration).isUndefined()) {
|
||||
proto = global->createBlankPrototype(cx, &StopIterationClass);
|
||||
if (!proto || !proto->freeze(cx))
|
||||
return false;
|
||||
|
||||
/* This should use a non-JSProtoKey'd slot, but this is easier for now. */
|
||||
if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto))
|
||||
return false;
|
||||
|
||||
MarkStandardClassInitializedNoProto(global, &StopIterationClass);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSObject *
|
||||
InitStopIterationClass(JSContext *cx, Handle<GlobalObject*> global)
|
||||
{
|
||||
RootedObject proto(cx, global->createBlankPrototype(cx, &StopIterationClass));
|
||||
if (!proto || !proto->freeze(cx))
|
||||
return NULL;
|
||||
|
||||
/* This should use a non-JSProtoKey'd slot, but this is easier for now. */
|
||||
if (!DefineConstructorAndPrototype(cx, global, JSProto_StopIteration, proto, proto))
|
||||
return NULL;
|
||||
|
||||
MarkStandardClassInitializedNoProto(global, &StopIterationClass);
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
js_InitIteratorClasses(JSContext *cx, JSObject *obj)
|
||||
{
|
||||
JS_ASSERT(obj->isNative());
|
||||
|
||||
Rooted<GlobalObject*> global(cx, &obj->asGlobal());
|
||||
|
||||
/*
|
||||
* Bail if Iterator has already been initialized. We test for Iterator
|
||||
* rather than for StopIteration because if js_InitIteratorClasses recurs,
|
||||
* as happens when the StopIteration object is frozen, initializing the
|
||||
* Iterator class a second time will assert.
|
||||
*/
|
||||
RootedObject iter(cx);
|
||||
if (!js_GetClassObject(cx, global, JSProto_Iterator, &iter))
|
||||
if (!GlobalObject::initIteratorClasses(cx, global))
|
||||
return NULL;
|
||||
if (iter)
|
||||
return iter;
|
||||
|
||||
if (!InitIteratorClass(cx, global) || !GlobalObject::initGeneratorClass(cx, global))
|
||||
return NULL;
|
||||
return InitStopIterationClass(cx, global);
|
||||
return global->getIteratorPrototype();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user